API

Открытый API сделок и вебхуки CRM

Обновлено 18 октября 2026 г.13 мин чтения

Сделки 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.