Интеграции и API

Клиентский API звонков и записей

Создание API ключей, выгрузка звонков и записей, webhooks, HMAC подпись, лимиты и диагностика.

25 минДля администратора

Клиентский API звонков 4phone

Это руководство описывает production API для выгрузки звонков и записей, настройку API ключей, webhook события и типовой порядок подключения клиентской CRM, ERP, BI или внутренней системы.

1. Возможности

Интеграция поддерживает два способа получения данных:

  • REST API для запроса списка звонков, карточки звонка и файла записи.
  • Webhooks для автоматического уведомления о завершении звонка и готовности файла записи.

Рекомендуемая схема работы:

  1. Получать новые события через webhooks.
  2. Сохранять звонок у себя по полю id.
  3. Скачивать запись после CALL_RECORDING_READY.
  4. Периодически сверять данные через REST API, чтобы восстановить события, пропущенные из-за недоступности клиентского endpoint.

2. Адреса

Базовый адрес REST API:

https://api.4phone.uz/api/v1/external

Адрес раздела API ключей:

https://4phone.uz/dashboard/settings/integrations/api-keys

Адрес раздела webhooks:

https://4phone.uz/dashboard/settings/integrations/webhooks

Адрес истории доставок:

https://4phone.uz/dashboard/settings/integrations/deliveries

Swagger поддерживается приложением, но в production по умолчанию отключен. Маршрут /api/docs появляется только при включенном SWAGGER_ENABLED. Клиентские интеграции должны опираться на это руководство и стабильный API v1.

3. Создание API ключа

Создавать и отзывать ключи могут пользователи с ролью OWNER, ADMIN или SUPERADMIN.

Порядок настройки:

  1. Откройте Настройки.
  2. Перейдите в Интеграции.
  3. Откройте API ключи.
  4. Нажмите Создать ключ.
  5. Укажите понятное название, например Bitrix export или BI production.
  6. Выберите scope calls:read.
  7. Выберите scope recordings:read, если клиенту нужны файлы записей.
  8. При необходимости ограничьте ключ отделом, IP адресами и сроком действия.
  9. Создайте ключ и сразу сохраните показанный секрет.

Полное значение ключа показывается только один раз. В базе хранится только SHA-256 хеш. Восстановить потерянный секрет нельзя. Нужно отозвать старый ключ и создать новый.

Для каждой внешней системы рекомендуется создавать отдельный ключ. Это позволяет независимо менять доступ, IP ограничения и срок действия.

3.1. Scopes

Для API звонков используются два разрешения:

  • calls:read разрешает список и карточку завершенных звонков.
  • recordings:read разрешает проверку и скачивание файлов записей.

Ключ только с recordings:read может скачать запись по известному id, но не может запросить список звонков. В обычной интеграции выбирайте оба scope.

3.2. Ограничение по отделу

Если поле Отдел не заполнено, ключ получает доступ ко всем разрешенным звонкам своего тенанта.

Если выбран отдел, API возвращает только звонки добавочных номеров этого отдела. Запрос добавочного другого отдела вернет пустой список. Карточка чужого звонка вернет 404.

Ограничение отдела применяется и к скачиванию записи.

3.3. IP whitelist

Поле IP whitelist принимает:

  • отдельный IPv4 адрес, например 45.147.253.34;
  • IPv4 подсеть, например 45.147.253.32/27;
  • отдельный IPv6 адрес;
  • IPv6 подсеть в CIDR формате.

Значения можно разделять запятой, пробелом или переносом строки. Если список пуст, ограничение по IP не применяется.

Указывайте публичный исходящий IP сервера, который обращается к API. При несовпадении API вернет 403 с кодом api_key.ip_not_allowed.

3.4. Срок действия и отзыв

Срок действия необязателен, но рекомендуется для временных интеграций и тестовых сред.

Отозванный или просроченный ключ больше не принимается. Отзыв необратим. Создание и отзыв ключей записываются в аудит платформы.

4. Авторизация запросов

Рекомендуемый заголовок:

X-Api-Key: cpbx_XXXXXXXX_SECRET

Также поддерживается Bearer формат:

Authorization: Bearer cpbx_XXXXXXXX_SECRET

Не передавайте ключ:

  • в query параметрах URL;
  • в имени файла;
  • в клиентском JavaScript браузера;
  • в сообщениях об ошибках и открытых логах.

Храните ключ в secret manager или переменной окружения:

export FOURPHONE_API_KEY='cpbx_XXXXXXXX_SECRET'

Проверка доступа:

curl -sS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls?limit=1"

5. Список звонков

GET /calls

Полный адрес:

https://api.4phone.uz/api/v1/external/calls

Требуемый scope: calls:read.

API возвращает только завершенные входящие и исходящие звонки. Внутренние звонки не входят в клиентскую выгрузку.

Доступные статусы:

  • ANSWERED
  • BUSY
  • NO_ANSWER
  • FAILED
  • CANCELLED

5.1. Фильтры

Поддерживаются параметры:

  • direction со значением INBOUND или OUTBOUND;
  • status со списком статусов через запятую;
  • startedFrom с началом периода в RFC 3339, граница включается;
  • startedTo с концом периода в RFC 3339, граница не включается;
  • phone с номером или частью номера;
  • extensionNumber с добавочным номером;
  • hasRecording со значением true или false;
  • limit от 1 до 100, значение по умолчанию 50;
  • cursor из поля meta.nextCursor предыдущего ответа.

В фильтре phone все символы, кроме цифр, удаляются. Поиск выполняется по номеру звонящего и номеру назначения.

hasRecording=true означает, что у звонка есть ссылка на запись. Файл может еще обрабатываться. Перед скачиванием проверяйте recording.available=true.

Пример запроса входящих отвеченных звонков:

curl -sS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls?direction=INBOUND&status=ANSWERED&startedFrom=2026-07-01T00%3A00%3A00%2B05%3A00&limit=50"

Пример поиска по номеру:

curl -sS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls?phone=998901234567&limit=50"

5.2. Формат ответа

{
  "data": [
    {
      "id": "847cc73c-6aaf-4a5e-aea8-d106fae10c45",
      "callId": "17ed2d85-a64a-4ce0-9151-a4dc57505dc6",
      "direction": "INBOUND",
      "status": "ANSWERED",
      "date": "23.07.2026 10:40:59",
      "dateIso": "2026-07-23T05:40:59Z",
      "timeZone": "Asia/Tashkent",
      "from": "998901234567",
      "to": "781139788",
      "durationSeconds": 83,
      "extensionNumber": "101",
      "recording": {
        "status": "AVAILABLE",
        "available": true,
        "fileName": "17ed2d85-a64a-4ce0-9151-a4dc57505dc6.wav",
        "contentType": "audio/wav",
        "sizeBytes": 1382400,
        "downloadUrl": "/api/v1/external/calls/847cc73c-6aaf-4a5e-aea8-d106fae10c45/recording"
      }
    }
  ],
  "meta": {
    "limit": 50,
    "hasMore": false,
    "nextCursor": null
  }
}

5.3. Значение полей

  • id является ID записи звонка. Используйте его для карточки и скачивания записи.
  • callId является техническим ID телефонной сессии.
  • direction содержит INBOUND или OUTBOUND.
  • status содержит итоговый статус.
  • date содержит локальную дату тенанта с точностью до секунды.
  • dateIso содержит то же время в UTC и RFC 3339.
  • timeZone указывает часовую зону, использованную для date.
  • durationSeconds содержит чистое время разговора в секундах. Для звонка без ответа значение равно 0.
  • extensionNumber содержит добавочный номер сотрудника или null.

Для входящего звонка:

  • from содержит внешний номер звонящего без знака +;
  • to содержит исходный DID, на который поступил звонок.

Для исходящего звонка:

  • from содержит Фамилия Имя Отчество, если все три поля заполнены у пользователя добавочного;
  • если ФИО заполнено не полностью, from содержит номер телефона;
  • to содержит внешний номер назначения без знака +.

5.4. Объект записи

Если запись не создавалась, поле recording равно null.

Возможные значения recording.status:

  • CAPTURED
  • ACCEPTED
  • UPLOADED
  • AVAILABLE
  • FAILED
  • ARCHIVED

Скачивание разрешено только при:

{
  "available": true
}

Значение available=true используется для статусов AVAILABLE и ARCHIVED. При остальных статусах downloadUrl равен null.

6. Пагинация

Результаты сортируются от новых к старым по времени начала звонка.

Если meta.hasMore=true, передайте meta.nextCursor без изменений:

curl -sS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls?limit=50&cursor=ЗНАЧЕНИЕ_ИЗ_NEXT_CURSOR"

Cursor привязан к:

  • тенанту;
  • отделу ключа;
  • направлению;
  • статусам;
  • периоду;
  • телефону;
  • добавочному;
  • фильтру записи.

Нельзя менять фильтры между страницами. При изменении фильтра начните запрос без cursor. Измененный или поврежденный cursor вернет 400.

Для регулярной синхронизации используйте период с небольшим перекрытием и удаляйте дубликаты по id. Это защищает от пропуска звонка при временной ошибке сети.

7. Карточка звонка

GET /calls/{id}

Требуемый scope: calls:read.

curl -sS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls/$CALL_ID"

Ответ содержит тот же объект, что элемент массива data.

API возвращает 404, если:

  • звонок не существует;
  • звонок принадлежит другому тенанту;
  • звонок находится в недоступном отделе;
  • звонок внутренний;
  • звонок еще не получил завершенный статус.

Одинаковый ответ 404 не раскрывает наличие чужих данных.

8. Получение записи

HEAD /calls/{id}/recording

Проверяет доступность и метаданные без передачи файла.

curl -sS -I \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  "https://api.4phone.uz/api/v1/external/calls/$CALL_ID/recording"

GET /calls/{id}/recording

Скачивает файл.

curl -fS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  -o call-recording.wav \
  "https://api.4phone.uz/api/v1/external/calls/$CALL_ID/recording"

Требуемый scope для обоих методов: recordings:read.

8.1. Каналы WAV

Если запись двухканальная:

  • файл остается в формате WAV;
  • первый канал содержит входящий поток исходной стороны вызова;
  • второй канал содержит исходящий поток в сторону исходной стороны вызова;
  • для входящего звонка это обычно клиент в первом канале и оператор во втором;
  • для исходящего звонка это обычно оператор в первом канале и клиент во втором;
  • запись продолжает работать при очередях и переводах вызова.

Старые записи могут оставаться одноканальными. Интеграция должна читать число каналов из заголовка WAV, а не определять его по дате или имени файла.

Метод GET передает исходный файл без перекодирования и сведения каналов, а HEAD возвращает его метаданные. URL, scope recordings:read, Range-запросы и остальные правила API для двухканальных записей не меняются. Объект recording в JSON не содержит число каналов, оно определяется из заголовка скачанного WAV.

Пример проверки файла:

ffprobe -v error \
  -select_streams a:0 \
  -show_entries stream=codec_name,sample_rate,channels,channel_layout \
  -of default=noprint_wrappers=1 \
  call-recording.wav

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

Ответ содержит:

  • Content-Type с форматом файла;
  • Content-Disposition с безопасным именем;
  • Content-Length, если размер известен;
  • Accept-Ranges: bytes;
  • Cache-Control: private, no-store.

Поддерживается частичная загрузка:

curl -fS \
  -H "X-Api-Key: $FOURPHONE_API_KEY" \
  -H "Range: bytes=0-1048575" \
  -o first-megabyte.bin \
  "https://api.4phone.uz/api/v1/external/calls/$CALL_ID/recording"

Для частичного ответа возвращается HTTP 206 и заголовок Content-Range.

9. Webhooks звонков

REST API подходит для выгрузки и сверки. Для получения звонков в реальном времени настройте webhook.

9.1. Создание

  1. Откройте Настройки.
  2. Перейдите в Интеграции.
  3. Откройте Webhooks.
  4. Нажмите Добавить webhook.
  5. Укажите название.
  6. Укажите публичный HTTPS URL клиентского обработчика.
  7. Выберите CALL_COMPLETED.
  8. Выберите CALL_RECORDING_READY, если нужна запись.
  9. Оставьте секрет пустым для безопасной автоматической генерации.
  10. Сохраните показанный секрет в secret manager клиентской системы.

Webhook URL должен:

  • использовать HTTPS;
  • не содержать логин и пароль;
  • иметь публичное DNS имя или публичный IP;
  • разрешаться только в публичные IP адреса;
  • быть доступен из интернета.

Локальные, loopback, link-local и приватные сети блокируются для защиты от SSRF. Политика URL проверяется при сохранении и перед каждой доставкой.

Секрет должен иметь длину от 16 до 256 символов. В базе он хранится в зашифрованном виде. После создания секрет повторно не показывается.

Если секрет потерян, нажмите Сменить секрет. После ротации клиентский обработчик нужно сразу переключить на новое значение.

9.2. CALL_COMPLETED

Событие создается после завершения обработки CDR для входящего или исходящего звонка. Внутренние звонки исключаются.

Если запись еще обрабатывается, событие уже содержит звонок, но:

{
  "recording": {
    "available": false,
    "downloadUrl": null
  }
}

9.3. CALL_RECORDING_READY

Событие создается отдельно, когда файл получает статус AVAILABLE или ARCHIVED. Проверка ожидающих записей выполняется каждые 30 секунд.

Событие содержит тот же data.id, что CALL_COMPLETED, и:

{
  "recording": {
    "available": true,
    "downloadUrl": "/api/v1/external/calls/847cc73c-6aaf-4a5e-aea8-d106fae10c45/recording"
  }
}

Если обработка записи завершилась статусом FAILED, CALL_RECORDING_READY не отправляется.

9.4. Формат webhook

POST /your/4phone/webhook
Content-Type: application/json
X-Webhook-Id: 8f4a3b2c-1234-4567-89ab-1234567890ab
X-Webhook-Timestamp: 1784770859
X-Webhook-Event: CALL_COMPLETED
X-Webhook-Signature: v1,abcdef1234567890
{
  "id": "8f4a3b2c-1234-4567-89ab-1234567890ab",
  "event": "CALL_COMPLETED",
  "data": {
    "schemaVersion": 1,
    "id": "847cc73c-6aaf-4a5e-aea8-d106fae10c45",
    "callId": "17ed2d85-a64a-4ce0-9151-a4dc57505dc6",
    "direction": "INBOUND",
    "status": "ANSWERED",
    "date": "23.07.2026 10:40:59",
    "dateIso": "2026-07-23T05:40:59Z",
    "timeZone": "Asia/Tashkent",
    "from": "998901234567",
    "to": "781139788",
    "durationSeconds": 83,
    "extensionNumber": "101",
    "recording": {
      "status": "CAPTURED",
      "available": false,
      "fileName": "17ed2d85-a64a-4ce0-9151-a4dc57505dc6.wav",
      "contentType": "audio/wav",
      "sizeBytes": null,
      "downloadUrl": null
    }
  },
  "sentAt": "2026-07-23T05:40:59.000Z",
  "timestamp": 1784770859
}

id в корне является ID webhook сообщения. data.id является ID звонка.

9.5. Проверка HMAC подписи

Подпись вычисляется от исходных байтов HTTP body:

signedContent = X-Webhook-Id + "." + X-Webhook-Timestamp + "." + rawBody
signature = "v1," + HMAC_SHA256_HEX(secret, signedContent)

Важно использовать исходный body до JSON разбора. Повторная сериализация JSON может изменить байты и привести к несовпадению подписи.

Пример для Node.js и Express:

import crypto from "node:crypto";
import express from "express";

const app = express();
const secret = process.env.FOURPHONE_WEBHOOK_SECRET;

app.post(
  "/4phone/webhook",
  express.raw({ type: "application/json" }),
  (request, response) => {
    const messageId = String(request.header("X-Webhook-Id") || "");
    const timestamp = String(request.header("X-Webhook-Timestamp") || "");
    const received = String(request.header("X-Webhook-Signature") || "");
    const rawBody = request.body;
    const signedContent = Buffer.concat([
      Buffer.from(`${messageId}.${timestamp}.`),
      rawBody,
    ]);
    const expected =
      "v1," +
      crypto.createHmac("sha256", secret).update(signedContent).digest("hex");

    const valid =
      received.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));

    const age = Math.abs(Date.now() / 1000 - Number(timestamp));
    if (!valid || !Number.isFinite(age) || age > 300) {
      return response.status(401).json({ accepted: false });
    }

    const payload = JSON.parse(rawBody.toString("utf8"));

    // Сначала проверьте уникальность payload.id в своей базе.
    // Затем поставьте обработку в локальную очередь.

    return response.status(202).json({ accepted: true });
  },
);

Та же формула проверки применяется в Python и PHP: важно передать в HMAC исходные байты HTTP body без повторной сериализации JSON.

9.6. Идемпотентность

4phone использует доставку как минимум один раз. Клиент обязан корректно обрабатывать повторное сообщение.

Сохраняйте корневой id или X-Webhook-Id в таблице обработанных сообщений. Если такой ID уже существует, верните успешный HTTP ответ без повторного выполнения бизнес операции.

Для звонков также используйте data.id как уникальный ключ записи. События CALL_COMPLETED и CALL_RECORDING_READY имеют разные webhook ID, но один data.id.

9.7. Ответ и повторные попытки

Успешной считается доставка, при которой endpoint вернул HTTP статус от 200 до 299.

Требования к обработчику:

  • проверить подпись;
  • проверить timestamp;
  • проверить идемпотентность;
  • быстро сохранить событие или поставить его в локальную очередь;
  • ответить в течение 10 секунд.

При сетевой ошибке, timeout или неуспешном HTTP статусе выполняется до 8 попыток. Интервалы после ошибок: 30, 60, 120, 240, 480, 960 и 1920 секунд.

Для каждой попытки обновляются timestamp, sentAt и HMAC подпись. X-Webhook-Id остается прежним.

10. История доставок

В разделе Настройки, Интеграции, История доставок отображаются:

  • тип события;
  • целевой webhook;
  • статус;
  • последний HTTP код;
  • количество попыток;
  • последняя ошибка;
  • время каждой из последних попыток.

Статусы:

  • PENDING ожидает отправки;
  • PROCESSING отправляется сейчас;
  • DELIVERED успешно принят клиентом;
  • FAILED ожидает следующей попытки;
  • DLQ исчерпал автоматические попытки.

Для FAILED и DLQ доступен ручной Replay.

11. Rate limit

Лимиты API ключа считаются в Redis отдельно для каждого метода в окне 60 секунд:

  • до 300 запросов к обычному методу;
  • до 120 запросов к GET или HEAD записи.

Ответ содержит:

X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset

При превышении возвращается 429 и Retry-After: 60.

На внешнем Nginx дополнительно действует общий лимит API. Клиент должен обрабатывать 429, учитывать Retry-After и использовать backoff.

12. Ошибки

Ошибка возвращается в JSON:

{
  "statusCode": 403,
  "timestamp": "2026-07-30T12:41:59.676Z",
  "path": "/api/v1/external/calls?limit=1",
  "code": "api_key.scope_missing",
  "message": "Missing API key scopes: calls:read"
}

Основные HTTP статусы и коды:

  • 400 calls.invalid_date_range означает неверный диапазон дат;
  • 400 calls.invalid_phone означает отсутствие цифр в фильтре телефона;
  • 400 calls.invalid_cursor означает поврежденный cursor;
  • 400 calls.cursor_filter_mismatch означает изменение фильтров пагинации;
  • 401 api_key.required означает отсутствие ключа;
  • 401 api_key.invalid означает неверный или отозванный ключ;
  • 401 api_key.expired означает истекший ключ;
  • 403 api_key.ip_not_allowed означает несовпадение IP whitelist;
  • 403 api_key.scope_missing означает отсутствие нужного scope;
  • 404 calls.not_found означает недоступный звонок;
  • 404 recordings.not_found означает отсутствие или ошибку записи;
  • 409 recordings.not_ready означает, что файл еще обрабатывается;
  • 416 recordings.invalid_range означает неверный байтовый диапазон;
  • 429 rate_limit.exceeded означает превышение лимита ключа.

13. Пример регулярной синхронизации

Пример на Node.js:

const baseUrl = "https://api.4phone.uz/api/v1/external";
const apiKey = process.env.FOURPHONE_API_KEY;

async function request(path) {
  const response = await fetch(`${baseUrl}${path}`, {
    headers: {
      "X-Api-Key": apiKey,
    },
  });

  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(
      `4phone API ${response.status}: ${error.code || error.message || "unknown"}`,
    );
  }

  return response.json();
}

async function loadCalls(startedFrom) {
  const calls = [];
  let cursor = null;

  do {
    const query = new URLSearchParams({
      startedFrom,
      limit: "100",
    });

    if (cursor) {
      query.set("cursor", cursor);
    }

    const page = await request(`/calls?${query.toString()}`);
    calls.push(...page.data);
    cursor = page.meta.nextCursor;
  } while (cursor);

  return calls;
}

Сохраняйте id как уникальное поле. После успешного цикла обновляйте checkpoint по dateIso, оставляя небольшое временное перекрытие при следующем запросе.

14. Диагностика

API возвращает 401

Проверьте:

  • ключ передан в X-Api-Key или Bearer заголовке;
  • значение скопировано полностью;
  • ключ не отозван;
  • срок действия не истек.

API возвращает 403

Проверьте:

  • выбран scope calls:read;
  • для записи выбран scope recordings:read;
  • IP whitelist содержит публичный исходящий IP клиента.

Список пустой

Проверьте:

  • период и часовую зону в startedFrom и startedTo;
  • направление и статусы;
  • ограничение ключа по отделу;
  • значение extensionNumber;
  • что звонок не является внутренним.

Запись возвращает 409

Файл еще обрабатывается. Дождитесь CALL_RECORDING_READY или повторите проверку с backoff.

Webhook не создается

Проверьте:

  • webhook активен;
  • выбрано нужное событие;
  • URL использует HTTPS;
  • DNS возвращает только публичные IP;
  • секрет существует.

Webhook имеет FAILED или DLQ

Откройте историю доставок и проверьте:

  • HTTP код;
  • текст последней ошибки;
  • время ответа;
  • доступность TLS сертификата;
  • что endpoint отвечает быстрее 10 секунд.

После исправления используйте Replay.

HMAC подпись не совпадает

Проверьте:

  • используется исходный raw body;
  • секрет относится к этому webhook;
  • строка содержит ID, timestamp и body в правильном порядке;
  • перед hex значением есть префикс v1,;
  • серверное время синхронизировано;
  • после ротации используется новый секрет.

15. Безопасность

  • Создавайте отдельный ключ для каждой системы.
  • Выдавайте только необходимые scopes.
  • Ограничивайте production ключ публичным IP клиента.
  • Устанавливайте срок действия тестовым ключам.
  • Не храните ключи и webhook секреты в Git.
  • Не передавайте секреты в URL.
  • Не логируйте заголовки авторизации.
  • Проверяйте HMAC до JSON обработки.
  • Проверяйте окно timestamp не более 5 минут.
  • Обеспечивайте идемпотентность по webhook ID.
  • При утечке сразу отзывайте ключ или ротируйте webhook секрет.

16. Как это работает внутри 4phone

  1. FreeSWITCH завершает звонок и отправляет данные CDR в backend.
  2. Backend сохраняет CDR с тенантом, направлением, номерами, временем, добавочным и состоянием записи.
  3. Post-call обработчик формирует нормализованный объект API.
  4. Для входящих и исходящих звонков создается CALL_COMPLETED.
  5. Запись поступает через NFS и при необходимости архивируется в MinIO.
  6. После доступности файла создается CALL_RECORDING_READY.
  7. Событие сохраняется в durable delivery хранилище и отправляется через очередь.
  8. API ключ всегда ограничивает запрос тенантом, scopes, отделом и IP.
  9. Файл отдается потоком без раскрытия внутреннего пути хранилища.

Основные части реализации:

  • backend/src/modules/external-api/external-calls.controller.ts
  • backend/src/modules/external-api/services/external-calls.service.ts
  • backend/src/modules/calls/external-call-payload.service.ts
  • backend/src/modules/api-keys/api-keys.service.ts
  • backend/src/modules/calls/post-call.service.ts
  • backend/src/modules/deliveries/deliveries.service.ts
  • backend/src/modules/recordings/recordings.service.ts

17. Совместимость

Текущая версия payload:

{
  "schemaVersion": 1
}

Новые необязательные поля могут добавляться без смены URL API. Клиент не должен отклонять payload из-за неизвестного поля. Ломающие изменения должны публиковаться в новой версии API.