# Исходящий вебхук из CRM Voronka.pro контракт с внешними системами

## На примере модуля: Сделки

## 1. Назначение

Webhook отправляется из Voronka CRM при сохранении записи сделки и предназначен для передачи данных во внешние системы:

- интеграционные шины,
- CRM/ERP/BI,
- колл-трекинг,
- системы аналитики,
- собственные backend-сервисы.

Текущая реализация передаёт:

1. **плоский набор полей сделки**;
2. **обогащённые объектные данные**:
    
    
    - клиент,
    - статус сделки,
    - воронка,
    - стадия воронки,
    - локация;
3. **custom fields**;
4. **информацию об изменённых полях**.

---

## 2. HTTP-параметры запроса

### Метод

```http
POST

```

### Content-Type

```http
application/json; charset=UTF-8

```

### Тело запроса

JSON-объект.

---

## 3. Общая структура payload

Webhook отправляет **один JSON-объект**.  
Верхний уровень содержит:

- плоские поля сделки,
- системные поля интеграции,
- вложенные объектные блоки,
- блок изменений,
- блок `meta`.

Пример общей структуры:

```json
{
  "subject": "Сделка 07.03.2026 18:27",
  "related_to": 297041,
  "assigned_user_id": 57,
  "ssalesprocesses_no": "С-55732",
  "ssalesprocesses_status": "PLL_SALE_COMPLETED",
  "locations": "32263",
  "listsalesfunnel": "Default",
  "salesfunnelstage": "1::1",

  "id": 470906,
  "module": "SSalesProcesses",
  "label": "Сделка 07.03.2026 18:27",
  "event": "ssalesprocesses.saved",
  "record_url": "index.php?module=SSalesProcesses&view=Detail&record=470906",

  "changed_fields": ["ssalesprocesses_status"],
  "changes": {
    "ssalesprocesses_status": {
      "old": "PLL_SALE_OPEN",
      "new": "PLL_SALE_COMPLETED",
      "old_display": "В работе",
      "new_display": "Проведено"
    }
  },

  "client": {
    "id": 297041,
    "number": "КЛ-20860",
    "name": "ВладимирСРМ2",
    "phone": "79135883488",
    "other_phone": "",
    "email": "",
    "email2": "",
    "type": "",
    "industry": ""
  },

  "deal_status": {
    "id": 14,
    "value": "PLL_SALE_COMPLETED",
    "label": "Проведено"
  },

  "sales_funnel": {
    "id": 1,
    "value": "Default",
    "label": "Основная"
  },

  "sales_funnel_stage": {
    "raw": "1::1",
    "funnel_id": 1,
    "stage_id": 1,
    "value": "PLL_SALE_NEW",
    "label": "Создано"
  },

  "location": {
    "id": 32263,
    "name": "НОВОСИБИРСК"
  },

  "custom_fields": {
    "cf_4040": "",
    "cf_4055": null
  },

  "meta": {
    "record_id": 470906,
    "module": "SSalesProcesses",
    "label": "Сделка 07.03.2026 18:27",
    "event": "ssalesprocesses.saved",
    "record_url": "index.php?module=SSalesProcesses&view=Detail&record=470906",
    "sent_at": "2026-03-07T14:41:59+03:00",
    "changed_fields": ["ssalesprocesses_status"]
  }
}

```

---

# 4. Правила интерпретации данных

## 4.1. Плоские поля сделки

В корне JSON передаются поля сделки в “плоском” виде.

Примеры:

```json
{
  "subject": "Сделка 07.03.2026 18:27",
  "related_to": 297041,
  "assigned_user_id": 57,
  "ssalesprocesses_no": "С-55732",
  "ssalesprocesses_status": "PLL_SALE_COMPLETED",
  "leadsource": "vk",
  "listsalesfunnel": "Default",
  "salesfunnelstage": "1::1",
  "locations": "32263"
}

```

### Особенности:

- значения справочников и picklist-полей могут передаваться как **внутренние коды CRM**;
- для человекочитаемой интерпретации используйте соответствующие объектные блоки:
    
    
    - `deal_status`
    - `deal_type`
    - `sales_funnel`
    - `sales_funnel_stage`
    - `location`

---

## 4.2. Системные поля интеграции

В payload всегда присутствуют системные поля:

### `id`

Внутренний ID записи сделки в CRM.

Тип:

```json
number

```

Пример:

```json
"id": 470906

```

---

### `module`

Системное имя модуля CRM.

Тип:

```json
string

```

Значение:

```json
"SSalesProcesses"

```

---

### `label`

Человекочитаемое название записи.

Тип:

```json
string

```

Пример:

```json
"label": "Сделка 07.03.2026 18:27"

```

---

### `event`

Тип события webhook в текущей реализации.

Тип:

```json
string

```

Текущее значение:

```json
"ssalesprocesses.saved"

```

### Важно

На текущем уровне реализации событие **не различает**:

- создание,
- изменение,
- удаление.

То есть и создание, и обновление записи приходят как:

```json
"event": "ssalesprocesses.saved"

```

Для определения факта изменения следует использовать блок `changed_fields` / `changes`.

---

### `record_url`

Относительная ссылка на карточку записи в CRM.

Тип:

```json
string

```

Пример:

```json
"record_url": "index.php?module=SSalesProcesses&view=Detail&record=470906"

```

### Важно

Это **относительный URL**, не абсолютный.  
Если внешней системе нужен полный адрес, базовый домен должен быть известен отдельно.

---

# 5. Объект клиента

## Поле `client`

Содержит данные клиента, связанного со сделкой (`related_to`).

Пример:

```json
"client": {
  "id": 297041,
  "number": "КЛ-20860",
  "name": "ВладимирСРМ2",
  "phone": "79135883488",
  "other_phone": "",
  "email": "",
  "email2": "",
  "type": "",
  "industry": ""
}

```

### Поля объекта `client`

#### `id`

ID клиента в CRM.

Тип:

```json
number

```

#### `number`

Внутренний номер клиента.

Тип:

```json
string

```

#### `name`

Название клиента.

Тип:

```json
string

```

#### `phone`

Основной телефон клиента.

Тип:

```json
string

```

#### `other_phone`

Дополнительный телефон клиента.

Тип:

```json
string

```

#### `email`

Основной email клиента.

Тип:

```json
string

```

#### `email2`

Дополнительный email клиента.

Тип:

```json
string

```

#### `type`

Тип клиента.

Тип:

```json
string

```

#### `industry`

Отрасль клиента.

Тип:

```json
string

```

---

## Дополнительные упрощённые поля клиента в корне

Для удобства внешней обработки дублируются:

```json
"related_to_name": "ВладимирСРМ2",
"related_to_phone": "79135883488",
"related_to_email": ""

```

### Рекомендация

Для интеграций лучше использовать:

- `client.name`
- `client.phone`
- `client.email`

а поля `related_to_name` / `related_to_phone` / `related_to_email` воспринимать как совместимость/shortcut.

---

# 6. Статус сделки

## Сырые поля

```json
"ssalesprocesses_status": "PLL_SALE_COMPLETED"

```

Это внутренний код CRM.

---

## Человекочитаемое представление

```json
"ssalesprocesses_status_display": "Проведено"

```

---

## Объект `deal_status`

Пример:

```json
"deal_status": {
  "id": 14,
  "value": "PLL_SALE_COMPLETED",
  "label": "Проведено",
  "picklist_valueid": 899,
  "sort_order": 3,
  "color": "#8BC34A",
  "description": "",
  "type_status": 2,
  "icon": null
}

```

### Поля

#### `id`

Внутренний ID статуса в таблице CRM.

#### `value`

Системное значение статуса.

#### `label`

Человекочитаемый перевод статуса.

#### `picklist_valueid`

ID picklist-значения CRM.

#### `sort_order`

Порядок сортировки.

#### `color`

Цвет статуса.

#### `description`

Описание статуса.

#### `type_status`

Тип статуса.

#### `icon`

Иконка статуса, если определена.

### Рекомендация

Во внешней системе для отображения использовать:

- `deal_status.label`

Для хранения/синхронизации:

- `deal_status.value`

---

# 7. Тип сделки

Если поле типа сделки в записи заполнено, webhook может содержать:

- `ssalesprocesses_type`
- `ssalesprocesses_type_display`
- `deal_type`

Пример целевого формата:

```json
"deal_type": {
  "id": 1,
  "value": "PLL_NEW_SALES",
  "label": "Новые продажи",
  "sort_order": 1,
  "color": ""
}

```

### Важно

В конкретных записях тип сделки может отсутствовать, если поле в самой записи пустое.

---

# 8. Воронка

## Сырое поле

```json
"listsalesfunnel": "Default"

```

---

## Перевод

```json
"listsalesfunnel_display": "Основная"

```

---

## Объект `sales_funnel`

Пример:

```json
"sales_funnel": {
  "id": 1,
  "value": "Default",
  "label": "Основная",
  "sort_order": 1,
  "color": "#1976D2",
  "type_status": 0,
  "icon": {
    "type": "icon",
    "name": "fas fa-baby-carriage"
  },
  "description": ""
}

```

### Поля

#### `id`

ID воронки.

#### `value`

Системное значение воронки.

#### `label`

Человекочитаемое название.

#### `sort_order`

Порядок сортировки.

#### `color`

Цвет воронки.

#### `type_status`

Тип статуса воронки.

#### `icon`

Иконка воронки.

#### `description`

Описание.

### Рекомендация

Во внешней системе:

- для UI использовать `sales_funnel.label`
- для логики/сопоставления использовать `sales_funnel.value`

---

# 9. Стадия воронки

## Сырое поле

```json
"salesfunnelstage": "1::1"

```

Формат:

```text
{funnel_id}::{stage_id}

```

Пример:

- `1::1`
- `1::5`

---

## Перевод

```json
"salesfunnelstage_display": "Создано"

```

---

## Объект `sales_funnel_stage`

Пример:

```json
"sales_funnel_stage": {
  "raw": "1::1",
  "funnel_id": 1,
  "stage_id": 1,
  "value": "PLL_SALE_NEW",
  "label": "Создано",
  "picklist_valueid": 1080,
  "sort_order": 1,
  "color": "BBDEFB",
  "type_status": 0
}

```

### Поля

#### `raw`

Исходное значение CRM.

#### `funnel_id`

ID воронки.

#### `stage_id`

ID стадии.

#### `value`

Системное значение стадии.

#### `label`

Человекочитаемое название стадии.

#### `picklist_valueid`

ID picklist-значения.

#### `sort_order`

Порядок сортировки.

#### `color`

Цвет стадии.

#### `type_status`

Тип стадии.

### Рекомендация

Для интерфейса использовать:

- `sales_funnel_stage.label`

Для логики:

- `sales_funnel_stage.raw`
- или пару `funnel_id + stage_id`

---

# 10. Локация

## Сырое поле

```json
"locations": "32263"

```

Может содержать одну или несколько локаций.

---

## Основной объект `location`

Если локация одна, дополнительно отдаётся единичный объект:

```json
"location": {
  "id": 32263,
  "name": "НОВОСИБИРСК"
}

```

---

## Массив всех локаций

```json
"locations_object": [
  {
    "id": 32263,
    "name": "НОВОСИБИРСК"
  }
]

```

---

## Массив ID локаций

```json
"locations_ids": [32263]

```

### Рекомендация

Если внешняя система поддерживает мульти-локации:

- используйте `locations_object`

Если нужна одна основная локация:

- используйте `location`

---

# 11. Custom fields

Все custom fields сделки передаются в объекте:

```json
"custom_fields": {
  "cf_4040": "",
  "cf_4055": null,
  "cf_4059": null,
  "cf_4063": null,
  "cf_4065": null,
  "cf_4086": 0,
  "cf_4088": null,
  "cf_4089": null,
  "cf_4092": null
}

```

### Формат имён

Имена передаются в системном виде:

```text
cf_XXXX

```

### Типы значений

Могут быть:

- `string`
- `number`
- `null`

### Особенность текущей реализации

Часть custom fields дополнительно может дублироваться в корне payload:

```json
"cf_4055": null,
"cf_4059": null

```

### Рекомендация

Во внешней системе считать основным источником именно:

```json
custom_fields

```

---

# 12. Информация об изменениях

Если запись была изменена и CRM определила изменённые поля, webhook содержит:

## `changed_fields`

Массив имён изменённых полей:

```json
"changed_fields": ["ssalesprocesses_status"]

```

---

## `changes`

Объект с деталями изменений по каждому полю.

Пример:

```json
"changes": {
  "ssalesprocesses_status": {
    "old": "PLL_SALE_OPEN",
    "new": "PLL_SALE_COMPLETED",
    "old_display": "В работе",
    "new_display": "Проведено",
    "old_object": {
      "id": 18,
      "value": "PLL_SALE_OPEN",
      "label": "В работе"
    },
    "new_object": {
      "id": 14,
      "value": "PLL_SALE_COMPLETED",
      "label": "Проведено"
    }
  }
}

```

---

## Формат объекта изменения

### `old`

Старое raw-значение.

### `new`

Новое raw-значение.

### `old_display`

Человекочитаемое старое значение, если для поля доступно.

### `new_display`

Человекочитаемое новое значение.

### `old_object`

Расширенный объект старого значения для справочников/связей.

### `new_object`

Расширенный объект нового значения.

---

## Примечания по изменениям

1. Блок `changes` присутствует **только если CRM определила изменения**.
2. Не все поля обязательно будут иметь `old_display/new_display`.
3. Для обычных строковых полей изменение может выглядеть так:

```json
"changes": {
  "subject": {
    "old": "Старая тема",
    "new": "Новая тема"
  }
}

```

4. Для справочных полей (статус, воронка, стадия, локация, клиент) будет расширенная форма с `*_display` и `*_object`.

---

# 13. Блок `meta`

Пример:

```json
"meta": {
  "record_id": 470906,
  "module": "SSalesProcesses",
  "label": "Сделка 07.03.2026 18:27",
  "event": "ssalesprocesses.saved",
  "record_url": "index.php?module=SSalesProcesses&view=Detail&record=470906",
  "sent_at": "2026-03-07T14:41:59+03:00",
  "changed_fields": ["ssalesprocesses_status"]
}

```

### Поля `meta`

#### `record_id`

Дублирует `id`.

#### `module`

Дублирует `module`.

#### `label`

Дублирует `label`.

#### `event`

Дублирует `event`.

#### `record_url`

Дублирует `record_url`.

#### `sent_at`

Время отправки webhook в ISO 8601 с часовым поясом.

Пример:

```json
"2026-03-07T14:41:59+03:00"

```

#### `changed_fields`

Дублирует массив изменённых полей.

### Рекомендация

`meta` можно использовать как системный служебный блок для логирования и трассировки.

---

# 14. Форматы данных

## Даты и время

### Дата

Формат:

```text
YYYY-MM-DD

```

Пример:

```json
"dateevent": "2025-10-24"

```

### Дата-время

Формат:

```text
YYYY-MM-DD HH:MM:SS

```

Пример:

```json
"modifiedtime": "2026-03-07 14:41:59"

```

### ISO 8601

Используется в `meta.sent_at`:

```json
"sent_at": "2026-03-07T14:41:59+03:00"

```

---

## Числа

### Целые числа

Могут передаваться как JSON number:

```json
"assigned_user_id": 57

```

### Decimal / money

В текущем payload многие денежные значения передаются как **строки**, а не как JSON number:

```json
"cost": "0.00000000",
"amountprepay": "0.00000000"

```

### Рекомендация

Во внешней системе денежные поля парсить как decimal/string, не как integer.

---

## Пустые значения

Могут приходить в одном из вариантов:

- пустая строка `""`
- `null`
- `0`
- `"0.00000000"`

### Рекомендация

При проектировании внешней схемы учитывать, что CRM не полностью унифицирует “пустоту”.

---

# 15. Поля, которые рекомендуется использовать во внешней системе

## Для идентификации сделки

- `id`
- `ssalesprocesses_no`
- `label`

## Для отображения статуса

- `deal_status.label`

## Для логики статуса

- `deal_status.value`

## Для отображения воронки

- `sales_funnel.label`

## Для логики воронки

- `sales_funnel.value`

## Для отображения стадии

- `sales_funnel_stage.label`

## Для логики стадии

- `sales_funnel_stage.raw`
- или `funnel_id + stage_id`

## Для клиента

- `client.id`
- `client.name`
- `client.phone`

## Для локации

- `location.id`
- `location.name`

## Для аудита изменений

- `changed_fields`
- `changes`

---

# 16. Ограничения текущей версии контракта

На текущем уровне реализации необходимо учитывать следующие особенности:

## 16.1. Событие не различает create/update/delete

Сейчас всегда приходит:

```json
"event": "ssalesprocesses.saved"

```

## 16.2. `record_url` относительный

Во внешней системе нужно самостоятельно добавлять домен CRM, если нужен полный URL.

## 16.3. Не все поля БД сделки обязаны присутствовать

Webhook строится на основе активных полей модуля и enrichment-логики.  
Он **не является полным raw-дампом всей строки БД**.

## 16.4. Custom fields частично дублируются

`custom_fields` — основной источник custom-полей; часть `cf_*` может повторяться в корне.

---

# 17. Рекомендации по интеграции

## 17.1. Не полагаться только на raw-поля справочников

Например, вместо:

```json
"ssalesprocesses_status": "PLL_SALE_COMPLETED"

```

использовать:

```json
"deal_status": {
  "value": "PLL_SALE_COMPLETED",
  "label": "Проведено"
}

```

---

## 17.2. Для связи сделки с клиентом использовать `client.id`

Поле `related_to` оставлено как raw ID, но основной объект клиента — это `client`.

---

## 17.3. Для отслеживания изменений использовать `changes`

Если внешний сервис должен реагировать только на конкретные изменения, ориентироваться на:

- `changed_fields`
- `changes`

Пример:

- если меняется только статус, обрабатывать только `changes.ssalesprocesses_status`.

---

## 17.4. Денежные значения парсить как decimal/string

Не рассчитывать, что они всегда будут JSON number.

---

# 18. Минимальный обязательный набор для внешней обработки

Если внешней системе не нужен весь payload, минимум, который рекомендуется использовать:

```json
{
  "id": 470906,
  "module": "SSalesProcesses",
  "event": "ssalesprocesses.saved",
  "label": "Сделка 07.03.2026 18:27",
  "client": {
    "id": 297041,
    "name": "ВладимирСРМ2",
    "phone": "79135883488"
  },
  "deal_status": {
    "value": "PLL_SALE_COMPLETED",
    "label": "Проведено"
  },
  "sales_funnel": {
    "value": "Default",
    "label": "Основная"
  },
  "sales_funnel_stage": {
    "raw": "1::1",
    "label": "Создано"
  },
  "location": {
    "id": 32263,
    "name": "НОВОСИБИРСК"
  },
  "changed_fields": ["ssalesprocesses_status"],
  "changes": {
    "ssalesprocesses_status": {
      "old": "PLL_SALE_OPEN",
      "new": "PLL_SALE_COMPLETED",
      "old_display": "В работе",
      "new_display": "Проведено"
    }
  }
}

```

---

# 19. Рекомендуемая стратегия обработки на стороне внешней системы

1. Принять JSON.
2. Считать `id`, `module`, `event`.
3. Найти или создать запись по `id`.
4. Считать блок `client`.
5. Считать `deal_status`, `sales_funnel`, `sales_funnel_stage`, `location`.
6. Если есть `changed_fields`, использовать их для выборочной логики обновления.
7. Если нужны дополнительные поля — читать их из корня payload.
8. Для custom-полей использовать `custom_fields`.

---