Открытый API сделок и вебхуки CRM
Сделки Chato можно вести из своей программы. Например, сайт создаёт сделку по заявке, 1С закрывает её после оплаты, склад ставит задачу менеджеру. Обратно Chato сообщает вашей программе, что произошло в CRM: сделку перенесли, выиграли или сменили ответственного.
Всё настраивается в кабинете: CRM → ⋯ → Настройки CRM → API и вебхуки. Вкладку видят администратор и владелец. Там же есть ссылка на справочник всех запросов в Swagger: https://chato.kz/api/v1/public/docs. Описание в формате OpenAPI лежит по адресу https://chato.kz/api/v1/public/openapi.json.
Ключ API
Нажмите Новый ключ, дайте ему название («Сайт», «1С») и отметьте права:
| Право | Что разрешает |
|---|---|
deals:read |
читать сделки и их примечания |
deals:write |
создавать, менять, переносить, закрывать, удалять и восстанавливать сделки, писать примечания |
contacts:read / contacts:write |
искать и читать контакты / создавать и менять контакты |
tasks:read / tasks:write |
читать задачи / ставить и выполнять задачи |
pipelines:read |
читать воронки, этапы, доп. поля и причины отказа |
users:read |
читать список сотрудников (для responsibleUserId) |
messages:send |
писать клиентам |
Нажмите Создать ключ. Ключ показывается один раз, поэтому сразу скопируйте его. Chato хранит только отпечаток ключа и не сможет показать его снова. В списке ключей видно, кто выпустил ключ и когда им пользовались последний раз. После нажатия Отозвать ключ сразу перестаёт работать, и программа получает 401.
Храните ключ как пароль: на сервере, в переменных окружения. Никогда не кладите его в код сайта, который видит браузер посетителя.
Всё, что программа делает по ключу, записывается в историю сделки от имени того, кто выпустил ключ, с пометкой «через API». Ключ не может больше, чем его автор: если воронка скрыта от сотрудника, по его ключу она тоже скрыта. Если автор ключа ушёл из компании, ключ перестаёт работать (401). Автоматизации этапов, уведомления и живое обновление доски у коллег срабатывают так же, как при работе в кабинете.
Как отправлять запросы
https://chato.kz/api/v1/public
Authorization: Bearer chato_sk_...
Content-Type: application/json
Ключ можно передать и в заголовке X-Api-Key. Успешный ответ всегда лежит в поле data. Проверить ключ можно запросом GET /me: он вернёт компанию, id ключа и его права.
{ "data": { "workspaceId": "cmulak1n90001295a8kf4ruhe", "keyId": "cmv2sreij000j293db87lukst", "scopes": ["deals:read", "deals:write"] } }
- Лимит: 120 запросов в минуту на ключ. Счётчик свой у каждого ключа, поэтому одна программа не съедает лимит другой. В каждом ответе есть заголовки
X-RateLimit-Limit,X-RateLimit-RemainingиX-RateLimit-Reset(через сколько секунд счётчик обнулится). Сверх лимита придёт429с заголовкомRetry-After: через сколько секунд повторить. Если с одного IP-адреса за минуту пришло 30 запросов с неверным ключом, следующие запросы с этого адреса до конца минуты получат429. - Повтор без дублей: добавьте к
POSTзаголовокIdempotency-Key. Подойдёт любая строка до 255 символов, например id заявки в вашей системе. Повтор с тем же ключом в течение суток вернёт первый ответ с заголовкомIdempotent-Replayed: trueи не создаст вторую сделку. Пока первый запрос ещё выполняется, повтор получит409 IDEMPOTENCY_IN_PROGRESS. Тот же ключ с другим телом запроса даст422 IDEMPOTENCY_KEY_REUSED. Если первый запрос завершился ошибкой, повтор выполнится заново.
Ошибки
Ошибка всегда приходит в одном виде:
{
"error": {
"code": "REQUIRED_FIELDS",
"message": "Заполните поля, чтобы перенести в «Договор подписан»",
"textCode": "REQUIRED_FIELDS",
"params": { "stage": "Договор подписан" },
"details": {
"stageId": "dmo_p1_s5",
"stageName": "Договор подписан",
"missing": [{ "id": "dmo_f4", "key": "data_dogovora", "label": "Дата договора", "type": "DATE", "options": [] }]
}
},
"statusCode": 422
}
message всегда на русском. Чтобы показать ошибку на другом языке, переводите по code или textCode.
| Код | Когда |
|---|---|
400 VALIDATION_FAILED |
неверные данные; список ошибок в details.validation |
400 CONTACT_IDENTITY_REQUIRED |
для поиска или создания контакта нет ни телефона, ни e-mail |
400 PHONE_TOO_SHORT |
в телефоне меньше 10 цифр |
401 UNAUTHORIZED |
ключа нет, он неверный или отозван |
403 FORBIDDEN_SCOPE |
у ключа нет нужного права (какого — в params.scope) |
404 DEAL_NOT_FOUND и подобные |
такого объекта нет в вашей компании (чужие id всегда дают 404) |
404 CRM_DISABLED |
CRM в компании не включена |
409 IDEMPOTENCY_IN_PROGRESS |
запрос с тем же Idempotency-Key ещё выполняется |
422 REQUIRED_FIELDS |
для этапа нужны обязательные поля; какие, указано в details.missing |
422 LOSS_REASON_REQUIRED |
перенос в «Отказ» без причины, а воронка её требует |
422 IDEMPOTENCY_KEY_REUSED |
тот же Idempotency-Key пришёл с другим телом запроса |
429 RATE_LIMITED |
превышен лимит; смотрите Retry-After |
Справочники
curl https://chato.kz/api/v1/public/pipelines -H "Authorization: Bearer $CHATO_KEY"
Этот запрос вернёт воронки с этапами:
{
"data": [
{
"id": "cmulbined000f29fg1na5uhw2",
"name": "Продажи",
"position": 0,
"lossReasonRequired": true,
"stages": [
{ "id": "cmulbinee000g29fg7o2ep8ev", "name": "Новая заявка", "color": "#3B82F6", "position": 0, "kind": "OPEN" },
{ "id": "cmulbinee000j29fgem0qk8ti", "name": "Успешно", "color": "#10B981", "position": 3, "kind": "WON" },
{ "id": "cmulbinee000k29fgkfdfjzdk", "name": "Отказ", "color": "#EF4444", "position": 4, "kind": "LOST" }
]
}
]
}
kind — вид этапа: OPEN — рабочий, WON — «Успешно», LOST — «Отказ». lossReasonRequired: true — для «Отказа» нужна причина.
Остальные справочники:
GET /deal-fields— доп. поля:key(код поля, по нему записываются значения),label,type,options(варианты{id, label}),pipelineIds(в каких воронках поле есть; пустой список — во всех),requiredStageIds(на каких этапах поле обязательно);GET /loss-reasons— причины отказа{id, name};GET /users— сотрудники:id,name,email,role(нужно правоusers:read).
Сделки
Создать
curl -X POST https://chato.kz/api/v1/public/deals \
-H "Authorization: Bearer $CHATO_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: site-order-10542' \
-d '{
"pipelineId": "PIPELINE_ID",
"title": "Заявка с сайта",
"amount": 150000,
"contact": { "name": "Айгерим", "phone": "+7 701 555 11 22", "email": "aigerim@example.com" },
"tags": ["сайт"],
"fields": { "gorod": "almaty", "obem": 40 }
}'
Ответ 201:
{
"data": {
"id": "cmv2srw0l000l293dy0wyl3kk",
"title": "Заявка с сайта",
"amount": 150000,
"currency": "KZT",
"status": "open",
"pipeline": { "id": "cmulbined000f29fg1na5uhw2", "name": "Продажи" },
"stage": { "id": "cmulbinee000g29fg7o2ep8ev", "name": "Новая заявка", "kind": "OPEN" },
"contact": { "id": "cmulm6bkt0007299yv6tep4fx", "name": "Айгерим", "phone": "77015551122", "email": "aigerim@example.com", "createdAt": "2026-09-28T19:01:56.381Z" },
"responsible": { "id": "cmulak1n60000295aiftvjchb", "name": "Асхат", "email": "askhat@example.com" },
"tags": ["сайт"],
"fields": { "gorod": "almaty", "obem": 40 },
"source": "api",
"utm": null,
"description": null,
"lossReason": null,
"lossComment": null,
"conversationId": null,
"channelId": null,
"createdBy": { "id": "cmulak1n60000295aiftvjchb", "name": "Асхат", "email": "askhat@example.com" },
"createdAt": "2026-10-10T19:38:45.333Z",
"updatedAt": "2026-10-10T19:38:45.333Z",
"closedAt": null,
"deletedAt": null,
"contactCreated": true
}
}
Сделка в ответах всех запросов выглядит так же. status — open, won, lost или unsorted (неразобранная).
Контакт задаётся полем contactId или contact. По contact Chato сначала ищет человека: по телефону (последние 10 цифр, формат любой), затем по e-mail (без учёта регистра). Если не нашёл, создаёт нового. У найденного контакта заполняются только пустые поля. contactCreated в ответе показывает, создан ли новый контакт.
Если не указать pipelineId, сделка попадёт в первую воронку. Если не указать stageId, сделка попадёт в первый этап воронки. Без title название будет «Сделка: <имя контакта>». Ответственный по умолчанию — автор ключа; "responsibleUserId": null — без ответственного. Можно передать и description. Источник сделки — api.
Доп. поля передаются объектом fields по кодам из /deal-fields. Для списка передайте id варианта, для множественного выбора — массив id, для даты — строку вида 2026-09-28, для флажка — true или false. Неверное значение вернёт 400 с названием поля. Тегов можно передать до 20, каждый до 40 символов.
Изменить
curl -X PATCH https://chato.kz/api/v1/public/deals/DEAL_ID \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"amount": 180000, "tags": ["сайт", "vip"], "fields": {"oplata": "invoice"}}'
Меняется только присланное: title, amount, responsibleUserId, description, tags, fields, source. tags заменяют прежний список. null в поле очищает его. Можно передать и stageId (с lossReasonId, lossComment): тогда сделка перейдёт в этап по тем же правилам, что у /move.
Перенести, закрыть
# в этап — с теми же правилами, что на доске
curl -X POST https://chato.kz/api/v1/public/deals/DEAL_ID/move \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"stageId": "STAGE_ID", "fields": {"data_dogovora": "2026-09-28"}}'
# успешно
curl -X POST https://chato.kz/api/v1/public/deals/DEAL_ID/close \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"result": "won"}'
# отказом — с причиной из /loss-reasons
curl -X POST https://chato.kz/api/v1/public/deals/DEAL_ID/close \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"result": "lost", "lossReasonId": "REASON_ID", "lossComment": "выбрали другого"}'
move, close и restore возвращают сделку с кодом 200. Если у этапа есть обязательные поля, а в сделке они пустые, придёт 422 REQUIRED_FIELDS со списком полей. Повторите запрос, добавив их в fields. Перенос в «Отказ» через move тоже принимает lossReasonId и lossComment. close переносит сделку в первый этап «Успешно» или «Отказ» её воронки. Если нужен другой этап этого вида, передайте его stageId.
Найти, прочитать, удалить
curl 'https://chato.kz/api/v1/public/deals?pipelineId=PIPELINE_ID&status=open&limit=50' \
-H "Authorization: Bearer $CHATO_KEY"
Ответ: {"data": {"items": [...], "nextCursor": "..."}}. Новые сделки идут первыми. limit — от 1 до 100, по умолчанию 50. Следующая страница — тот же запрос с &cursor=<nextCursor>. nextCursor: null — страниц больше нет.
Фильтры:
pipelineId,stageId;status:open,won,lost,closed(выигранные и проигранные),unsortedилиall(по умолчанию; все, кроме удалённых);responsibleUserId(илиnone— без ответственного),contactId,tag;search— по названию, имени или номеру клиента;updatedSinceиcreatedSince— ISO-дата. Удобно для синхронизации: «что изменилось с прошлого раза».
Остальные запросы:
GET /deals/DEAL_ID— одна сделка;DELETE /deals/DEAL_ID— в корзину на 30 дней (ответ204без тела);POST /deals/DEAL_ID/restore— вернуть из корзины.
Примечания и задачи
curl -X POST https://chato.kz/api/v1/public/deals/DEAL_ID/notes \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"text": "Оплата прошла в 1С"}'
curl -X POST https://chato.kz/api/v1/public/deals/DEAL_ID/tasks \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"kind": "CALL", "text": "Перезвонить", "dueAt": "2026-10-12T09:00:00+05:00"}'
curl -X POST https://chato.kz/api/v1/public/tasks/TASK_ID/complete \
-H "Authorization: Bearer $CHATO_KEY" -H 'Content-Type: application/json' \
-d '{"result": "Дозвонились"}'
Ответ на создание задачи:
{
"data": {
"id": "cmv2ssf3x000y293dabkcfivc",
"dealId": "cmv2srw0l000l293dy0wyl3kk",
"contactId": "cmulm6bkt0007299yv6tep4fx",
"kind": "CALL",
"customType": null,
"text": "Перезвонить",
"dueAt": "2026-10-12T04:00:00.000Z",
"allDay": false,
"responsible": { "id": "cmulak1n60000295aiftvjchb", "name": "Асхат", "email": "askhat@example.com" },
"createdBy": { "id": "cmulak1n60000295aiftvjchb", "name": "Асхат", "email": "askhat@example.com" },
"completedAt": null,
"completedBy": null,
"result": null,
"createdAt": "2026-10-10T19:39:10.078Z"
}
}
Списки — GET /deals/DEAL_ID/notes и GET /deals/DEAL_ID/tasks. Примечание приходит в виде {id, dealId, text, author, pinned, createdAt}.
Поля задачи:
dueAt— срок, обязателен;kind— вид задачи:CALL(звонок, по умолчанию),MEETING(встреча),WRITE(написать),OTHERили свой вид (CUSTOMвместе сcustomTypeId);allDay— задача на весь день;responsibleUserId— исполнитель. По умолчанию задачу получает ответственный сделки.
Контакты
GET /contacts?phone=87015551122или?email=...— найти, вернётся до 20 контактов. Безphoneиemailпридёт400;GET /contacts/CONTACT_ID— один контакт;POST /contactsс телом{name, phone, email}— создать. Если человек уже есть, вернётся существующий контакт (200,"created": false). Новый контакт вернётся с кодом201и"created": true;PATCH /contacts/CONTACT_ID— изменить имя, телефон или e-mail.
Контакт: {id, name, phone, email, createdAt}.
Написать клиенту
Нужно право messages:send. Запрос POST /messages. В теле укажите conversationId или phone, для нового номера — ещё channelId. Добавьте text и/или fileUrl (можно с fileName). Работает так же, как действие message входящей ссылки.
{ "data": { "status": "sent", "conversationId": "cmt9x1a2b0003o701abcd1234" } }
Если диалога с этим номером нет и channelId не передан, придёт 404.
Вебхуки: события сделок на ваш адрес
Нажмите Добавить адрес, впишите адрес https://… и отметьте события. После сохранения у адреса появится свой секрет подписи: увидеть его можно кнопкой «Показать». В компании может быть до 10 адресов.
| Событие | Когда |
|---|---|
deal.created |
сделка создана (в кабинете, по API, из обращения) |
deal.updated |
изменились название, сумма, теги, описание, источник, доп. поля или ответственный |
deal.stage_changed |
сделка перешла в другой этап |
deal.won / deal.lost |
сделка закрыта успешно / отказом |
deal.deleted / deal.restored |
сделка удалена в корзину / восстановлена из корзины |
deal.responsible_changed |
сменился ответственный |
task.created / task.completed |
задача поставлена / выполнена |
note.created |
добавлено примечание |
На тот же адрес можно получать и события чатов: message.received, conversation.closed и другие (см. Вебхуки). В событиях чатов в data лежат те же поля, что у адреса ассистента.
Chato отправляет POST с таким телом (сделка сокращена):
{
"id": "evt_28b44037b3de48b5b944430d9546c5c1",
"event": "deal.stage_changed",
"createdAt": "2026-09-29T04:46:42.196Z",
"workspaceId": "cmulak1n90001295a8kf4ruhe",
"data": {
"deal": {
"id": "cmulms3cp000r29eujik2dr1e",
"title": "Заявка с сайта",
"amount": 150000,
"currency": "KZT",
"status": "open",
"pipeline": { "id": "...", "name": "Продажи" },
"stage": { "id": "...", "name": "Счёт выставлен", "kind": "OPEN" },
"contact": { "id": "...", "name": "Айгерим", "phone": "77015551122", "email": "aigerim@example.com", "createdAt": "2026-09-28T19:01:56.381Z" },
"responsible": { "id": "...", "name": "Асхат", "email": "askhat@example.com" },
"tags": ["сайт"],
"fields": { "gorod": "almaty" },
"closedAt": null
},
"changes": {
"stage": {
"from": { "id": "...", "name": "В работе", "kind": "OPEN" },
"to": { "id": "...", "name": "Счёт выставлен", "kind": "OPEN" }
}
},
"actor": { "type": "user", "userId": "cmulak1n60000295aiftvjchb" }
}
}
Что лежит в data:
deal— снимок сделки целиком, в том же виде, что отдаёт API;changes— что изменилось, в видеfrom→to. Ключи:title,amount,tags,description,source,responsible,stage,fields.<код>;actor.type— кто сделал изменение:user(сотрудник в кабинете),api,webhook,automation(тогда рядом естьautomationId) илиsystem;reason— уdeal.deletedобъясняет причину удаления;taskилиnote— у событийtask.*иnote.createdлежат рядом сdeal.
Заголовки запроса:
X-Chato-Event— событие;X-Chato-Event-Id— одинаков у всех повторов, по нему отбрасывайте дубли;X-Chato-Delivery— id доставки;X-Chato-Timestamp— время отправки, секунды Unix;X-Chato-Signature— подпись;User-Agent: Chato-Webhooks/1.0.
Проверка подписи
X-Chato-Signature — это sha256= плюс HMAC-SHA256 от строки <X-Chato-Timestamp>.<сырое тело>, посчитанный секретом адреса (секрет начинается с whsec_). Считайте подпись по сырому телу, до разбора JSON. Запросы старше 5 минут отбрасывайте.
Node.js (Express):
const crypto = require('node:crypto');
app.post('/chato', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.get('X-Chato-Timestamp');
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.CHATO_WEBHOOK_SECRET)
.update(`${ts}.${req.body}`)
.digest('hex');
const given = req.get('X-Chato-Signature') || '';
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || given.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
res.sendStatus(200); // ответьте сразу, тяжёлую работу — в фон
});
PHP:
$body = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_CHATO_TIMESTAMP'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, getenv('CHATO_WEBHOOK_SECRET'));
$given = $_SERVER['HTTP_X_CHATO_SIGNATURE'] ?? '';
if (abs(time() - (int) $ts) > 300 || !hash_equals($expected, $given)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
http_response_code(200);
Python (Flask):
import hmac, hashlib, os, time
from flask import request, abort
@app.post("/chato")
def chato():
body = request.get_data() # сырые байты
ts = request.headers.get("X-Chato-Timestamp", "")
expected = "sha256=" + hmac.new(
os.environ["CHATO_WEBHOOK_SECRET"].encode(), f"{ts}.".encode() + body, hashlib.sha256
).hexdigest()
given = request.headers.get("X-Chato-Signature", "")
if abs(time.time() - int(ts or 0)) > 300 or not hmac.compare_digest(expected, given):
abort(401)
event = request.get_json()
return "", 200
Повторы, журнал, автоотключение
- Доставка считается успешной, если ваш сервер ответил любым кодом
2xxв течение 10 секунд. Иначе Chato повторит запрос через 1 мин, 5 мин, 30 мин, 2 ч и 6 ч. Всего шесть попыток за 8,5 часа. Перенаправления (3xx) Chato не выполняет и считает неудачей. - Под каждым адресом есть журнал доставок: статус, код ответа, число попыток, что отправлено и что ответил ваш сервер. Кнопка Отправить ещё раз повторяет доставку с тем же
idсобытия. Журнал хранится 30 дней. - Адрес выключается сам, если подряд не прошли 25 попыток и неудачи длятся больше суток. Владелец и администраторы получают уведомление. Почините приёмник и нажмите Включить.
- Проверить отправляет тестовое событие
pingсdata: {"message": "Проверка адреса вебхука Chato"}. - Новый секрет выпускает новый секрет подписи. Старый перестаёт подходить сразу.
Отвечайте 200 сразу, а обработку делайте в фоне. Долгий ответ Chato посчитает неудачей и повторит запрос. Повтор придёт с тем же X-Chato-Event-Id, и его легко узнать.
Сделки через входящую ссылку
Если вы уже пользуетесь входящей ссылкой ассистента, она умеет работать и со сделками. Правила те же, что у API, а в истории сделки будет пометка «через вебхук». Действия выполняются от имени владельца компании.
{ "action": "deal.create", "contact": { "name": "Айгерим", "phone": "+77015551122" }, "deal": { "title": "Заказ из 1С", "amount": 77000 } }
{ "action": "deal.move", "dealId": "DEAL_ID", "stageId": "STAGE_ID" }
{ "action": "deal.note", "dealId": "DEAL_ID", "text": "Оплата прошла" }
В ответе придут status (deal_created, deal_moved или note_added) и deal, а для deal.note — ещё note.



