Кастомна телефонна інтеграція з CallAIder: навіщо потрібна, як детально реалізувати API і як підключити на платформі

Кастомна інтеграція потрібна, коли ви хочете підключити до CallAIder власну телефонну систему або внутрішній API, який не входить до стандартних конекторів.

Цей матеріал побудований у правильній послідовності для впровадження:

  1. Спочатку розбираємо, навіщо це потрібно бізнесу і команді.
  2. Потім йдемо у детальну технічну реалізацію API за контрактом.
  3. І тільки після цього показуємо, як підключити все в інтерфейсі платформи.

Якщо ви технічний лід, CTO, інтегратор або backend-розробник, цей гайд можна використовувати як практичний чеклист запуску.

Чому варто робити кастомну інтеграцію і яку цінність вона дає

Багато команд бачать інтеграцію лише як «щоб діалоги підтягувалися». Насправді кастомна інтеграція вирішує значно ширші задачі.

1) Єдине джерело правди для аналітики

Ви самі визначаєте, які саме дзвінки вважаються валідними для аналізу, які атрибути передавати і як називати властивості. Це прибирає розрив між даними у вашій телефонії/CRM і даними в аналітиці CallAIder.

Результат:

  • менше суперечок про «чому тут інші цифри»;
  • прозорий аудит даних;
  • контроль якості на вашому боці.

2) Повніші дані про кожен дзвінок

Через properties ви можете передавати те, чого часто немає у стандартних інтеграціях:

  • відділ або черга;
  • сегмент клієнта;
  • місто/регіон;
  • бізнес-статус (наприклад, «питання вирішено»);
  • внутрішні скоринги якості або пріоритети.

Ці дані стають основою для точної вибірки, порівняння команд і побудови релевантних зрізів.

3) Точна фільтрація під реальний бізнес-процес

Завдяки propertyFilters ви можете не просто «тягнути всі дзвінки», а відбирати їх за бізнес-логікою:

  • тільки підтримка і тільки VIP;
  • тільки вхідні звернення з певних міст;
  • тільки дзвінки з оцінкою якості в заданому діапазоні.

Це особливо важливо, коли різні команди мають різні KPI і не повинні аналізувати один і той самий масив розмов.

4) Безпека та керованість інтеграції

Механізм X-Secret-Key дозволяє контролювати авторизований доступ до endpoint-ів. Ви зберігаєте повний контроль над тим, які запити приймаються, і можете додати власні правила безпеки: rate limiting, IP allowlist, журналювання, ротацію ключів.

5) Масштабування без ручних операцій

Коли процес налаштований правильно, команда не витрачає час на ручний експорт/імпорт дзвінків. Система стабільно постачає дані, а аналітика працює у фоновому режимі.

6) Готовність до складних сценаріїв

Кастомний API дає гнучкість для сценаріїв, де стандартний конектор майже завжди обмежений:

  • кілька АТС або кілька джерел даних;
  • внутрішні правила нормалізації номерів і операторів;
  • додаткові поля з CRM або ERP;
  • особливі вимоги до зберігання й доступу до записів.

Коли саме варто обирати Custom Telephony

Кастомна інтеграція найкраще підходить, якщо:

  • у вас власна телефонія або нестандартний провайдер;
  • потрібні додаткові властивості дзвінків для фільтрації;
  • важливо контролювати контракт даних і якість імпорту;
  • у вас кілька відділів/команд з різними правилами обробки;
  • потрібна предиктивна або сегментна аналітика на базі внутрішніх бізнес-ознак.

Як виглядає потік даних end-to-end

У реальному потоці це працює так:

  1. Ви підключаєте інтеграцію в кабінеті.
  2. CallAIder перевіряє доступність через /health.
  3. Для списку операторів запитує /contacts.
  4. Для імпорту діалогів запитує /calls із часовим діапазоном, напрямком, операторами та propertyFilters.
  5. Для відтворення/аналізу аудіо звертається до /calls/:id/recording.
  6. Якщо реалізовано /properties/schema, у майстрі команди з’являється крок налаштування властивостей.

Що підготувати до початку розробки API

Перед імплементацією узгодьте всередині команди:

  • базовий URL API (публічний і доступний ззовні);
  • секретний ключ для X-Secret-Key;
  • мапінг полів між джерелом дзвінків і контрактом CallAIder;
  • формат і словник ключів у properties;
  • політику доступу до записів дзвінків (постійні або тимчасові URL);
  • логування і моніторинг помилок інтеграції.

Реалізація API: повна технічна специфікація і практичні деталі

Нижче - центральна частина гайду. Саме її потрібно реалізувати на вашому бекенді.

Загальні вимоги до всіх endpoint-ів

  • Приймайте і віддавайте JSON.
  • Перевіряйте X-Secret-Key на кожному endpoint.
  • Повертайте коректні HTTP-коди (200, 401, 404, 500).
  • Для помилок використовуйте єдину структуру відповіді.
  • Працюйте тільки з завершеними дзвінками.

Рекомендований формат помилки:

{
  "status": "error",
  "message": "Опис помилки"
}

Автентифікація через X-Secret-Key

У кожному запиті від CallAIder буде:

X-Secret-Key: <ваш_секретний_ключ>

Що обов’язково реалізувати:

  • перевірку заголовка до бізнес-логіки;
  • повернення 401 Unauthorized, якщо ключ відсутній або не збігся;
  • журналювання відхилених запитів (без виводу секрета в лог).

Мінімальний middleware-приклад:

function auth(req, res, next) {
  if (req.headers['x-secret-key'] !== process.env.CALLAIDER_SECRET) {
    return res.status(401).json({ status: 'error', message: 'Unauthorized' });
  }
  next();
}

Endpoint 1: GET /health

Призначення: перевірка доступності API при підключенні інтеграції.

Запит:

GET {your_api_url}/health
X-Secret-Key: <secret>

Відповідь 200 OK:

{ "status": "success" }

Практична порада: цей endpoint має бути максимально легким і швидким, без важких операцій.

Endpoint 2: GET /contacts

Призначення: повернути операторів (співробітників), доступних для команди.

Запит:

GET {your_api_url}/contacts
X-Secret-Key: <secret>

Відповідь 200 OK:

{
  "contacts": [
    {
      "id": "101",
      "phone": "+380971234567",
      "name": "Іван Петренко",
      "department": "Продажі"
    }
  ]
}

Поля:

  • id (string, обов’язково) - стабільний унікальний ID оператора;
  • phone (string, обов’язково) - номер оператора;
  • name (string, обов’язково) - ім’я оператора;
  • department (string, опційно) - назва відділу.

Рекомендації:

  • не змінюйте id одного й того ж оператора між синхронізаціями;
  • віддавайте актуальний склад команди;
  • якщо є і зовнішній, і внутрішній номер - визначте сталу політику, який саме передаєте у phone.

Endpoint 3: POST /calls

Призначення: повернути список завершених дзвінків за період і фільтрами.

Запит:

POST {your_api_url}/calls
Content-Type: application/json
X-Secret-Key: <secret>

Приклад тіла запиту:

{
  "startTime": 1709500000,
  "stopTime": 1709586400,
  "direction": "incoming",
  "operators": ["101", "102"],
  "propertyFilters": {
    "Відділ": ["продажі", "підтримка"],
    "Оцінка якості": {
      "mode": "all",
      "conditions": [
        { "operator": "gt", "value": 2 },
        { "operator": "lt", "value": 8 }
      ]
    }
  }
}

Розбір полів запиту:

  • startTime (number, обов’язково) - початок періоду (Unix timestamp, секунди);
  • stopTime (number, обов’язково) - кінець періоду (Unix timestamp, секунди);
  • direction (string, опційно) - incoming або outgoing;
  • operators (string[], опційно) - фільтр за ID операторів;
  • propertyFilters (object, опційно) - додаткова фільтрація.

Формати propertyFilters

Підтримуються три основні формати:

  1. Точний збіг значення:
{ "Місто клієнта": "Київ" }
  1. Один із кількох варіантів:
{ "Відділ": ["продажі", "підтримка"] }
  1. Числові умови з логікою all або any:
{
  "Оцінка якості": {
    "mode": "any",
    "conditions": [
      { "operator": "gt", "value": 10 },
      { "operator": "lt", "value": 5 }
    ]
  }
}

Оператори для числових умов:

  • eq - дорівнює;
  • ne - не дорівнює;
  • gt - більше;
  • gte - більше або дорівнює;
  • lt - менше;
  • lte - менше або дорівнює.

Якщо mode не передано, використовується all.

Як правильно реалізувати фільтрацію

Є два допустимі режими роботи:

  1. Рекомендований: фільтрувати одразу у вашому API за всіма вхідними параметрами, включно з propertyFilters.
  2. Спрощений: ігнорувати propertyFilters на вашому боці, але обов’язково повертати properties, щоб CallAIder застосував постфільтрацію після імпорту.

Ключове уточнення зі специфікації: реалізація фільтрації propertyFilters саме на вашому API не є обов’язковою. Якщо ваш backend не підтримує ці фільтри, інтеграція все одно коректно працює: ви можете повернути всі завершені дзвінки в межах startTime, stopTime, direction, operators, а платформа CallAIder виконає додаткову фільтрацію на своїй стороні за об’єктом properties.

Щоб такий fallback-режим працював правильно, дотримуйтесь правил:

  • передавайте properties у кожному дзвінку, де потрібна додаткова фільтрація;
  • якщо /properties/schema реалізовано, ключі properties мають збігатися з key зі схеми;
  • якщо /properties/schema не реалізовано, ключі можуть бути довільними, але значення мають бути простих типів: string, number, boolean.

Відповідь POST /calls

Приклад 200 OK:

{
  "calls": [
    {
      "id": "call-abc-123",
      "clientNumber": "+380501234567",
      "clientName": "Client One",
      "direction": "incoming",
      "duration": 185,
      "startTime": 1709586000,
      "endTime": 1709586185,
      "operatorId": "101",
      "operatorName": "John Carter",
      "operatorNumber": "7545",
      "whoHungUp": "client",
      "properties": {
        "Відділ": "підтримка",
        "Місто клієнта": "Львів",
        "Питання вирішено": true,
        "Оцінка якості": 72
      }
    }
  ]
}

Поля дзвінка:

  • id (string, обов’язково) - унікальний ID дзвінка;
  • clientNumber (string, опційно) - номер клієнта;
  • clientName (string, опційно) - ім’я клієнта;
  • direction (string, обов’язково) - incoming або outgoing;
  • duration (number, обов’язково) - тривалість у секундах;
  • startTime (number, обов’язково) - початок дзвінка (Unix timestamp, секунди);
  • endTime (number, опційно) - завершення дзвінка (Unix timestamp, секунди);
  • operatorId (string, обов’язково) - ID оператора;
  • operatorName (string, обов’язково) - ім’я оператора;
  • operatorNumber (string, обов’язково) - номер оператора;
  • whoHungUp (string, опційно) - operator, client або system;
  • properties (object, опційно) - додаткові властивості дзвінка.

Критично важливо:

  • повертайте лише завершені дзвінки;
  • не віддавайте активні або пропущені виклики;
  • якщо реалізовано /properties/schema, ключі у properties мають збігатися з key зі схеми.

Додаткове важливе уточнення:

  • поле properties загалом не є обов’язковим для базового імпорту завершених діалогів;
  • але якщо ви хочете використовувати додаткову фільтрацію (під час імпорту команди або у фільтрах діалогів), properties потрібно передавати;
  • саме properties використовуються платформою як джерело додаткових ознак для фільтрації.

Endpoint 4: GET /properties/schema (необов’язковий; потрібен для гнучкого налаштування імпорту)

Цей endpoint не є обов’язковим для роботи інтеграції. Його призначення вузьке і конкретне: дати можливість гнучко налаштовувати імпорт діалогів на платформу через додаткові фільтри під час створення команди.

Тобто /properties/schema потрібен не для базового імпорту як такого, а для керованого UI-кроку «Властивості» у майстрі команди.

Запит:

GET {your_api_url}/properties/schema
X-Secret-Key: <secret>

Приклад відповіді:

{
  "properties": [
    { "key": "Відділ", "type": "enum", "values": ["продажі", "підтримка", "утримання"] },
    { "key": "Сегмент клієнта", "type": "enum", "values": ["стандарт", "vip"] },
    { "key": "Місто клієнта", "type": "string" },
    { "key": "Питання вирішено", "type": "boolean" },
    { "key": "Оцінка якості", "type": "number" }
  ]
}

Поля схеми:

  • key (string, обов’язково) - назва властивості;
  • type (string, обов’язково) - string, number, boolean або enum;
  • values (array, опційно) - допустимі значення.

Якщо endpoint не реалізований:

  • можна повернути { "properties": [] }, або
  • 404 Not Found.

Інтеграція продовжить працювати, але платформа пропустить крок гнучкого налаштування властивостей у майстрі команди.

Важливо розуміти різницю між schema і properties:

  • /properties/schema описує, які фільтри показати користувачу під час налаштування команди;
  • calls[].properties містить фактичні значення властивостей конкретного дзвінка;
  • calls[].properties використовуються платформою для додаткової фільтрації під час імпорту та також для додаткової фільтрації у списку діалогів.

Endpoint 5: GET /calls/:id/recording

Призначення: повернути посилання на запис конкретного дзвінка.

Запит:

GET {your_api_url}/calls/123/recording
X-Secret-Key: <secret>

Відповідь 200 OK:

{
  "url": "https://your-storage.com/recordings/call-abc-123.mp3",
  "filename": "call_recording_call-abc-123.mp3",
  "mimeType": "audio/mpeg"
}

Поля:

  • url (string, обов’язково) - пряме посилання на файл;
  • filename (string, опційно) - назва файлу;
  • mimeType (string, опційно) - MIME-тип (audio/mpeg, audio/wav тощо).

Якщо запис недоступний, поверніть 404 Not Found.

Рекомендація: якщо URL тимчасовий, забезпечте строк життя не менше 30 хвилин.

Коди відповідей і єдина обробка помилок

Використовуйте передбачувану матрицю:

  • 200 - успіх;
  • 401 - невалідний/відсутній секретний ключ;
  • 404 - ресурс не знайдено;
  • 500 - внутрішня помилка.

Практичні рекомендації для production

Окрім базового контракту, варто додати:

  • ідемпотентність за call.id, щоб уникати дублювань;
  • логування запитів/відповідей з кореляційним ID;
  • таймаути і retry до внутрішніх джерел;
  • моніторинг частоти помилок по кожному endpoint;
  • алерти на різке падіння кількості імпортованих дзвінків.

Підключення інтеграції в платформі CallAIder (після реалізації API)

Коли API уже реалізовано і перевірено, переходьте до підключення в інтерфейсі.

Крок 1. Створіть підключення Custom API

  1. Відкрийте розділ Інтеграції.
  2. Знайдіть картку Кастомне API.
  3. Натисніть Підключити.

Картка кастомної інтеграції у списку інтеграцій

У формі підключення заповніть:

  • Назва підключення;
  • API URL;
  • Secret Key.

Заповнення API URL і Secret Key для кастомної інтеграції

Після збереження система перевірить endpoint /health.

Крок 2. Створіть команду на цій інтеграції

Далі треба створити команду, яка визначає:

  • операторів для імпорту;
  • правило аналітики;
  • властивості для додаткового відбору дзвінків.

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

Крок 3. Налаштуйте властивості в майстрі команди

Якщо ви реалізували /properties/schema, у майстрі з’явиться крок Властивості, де можна задати додаткові умови вибірки.

Крок Властивості під час створення команди з додатковими фільтрами

Це і є прямий зв’язок між вашим API-контрактом та бізнес-логікою імпорту на платформі.

Фільтрація діалогів за додатковими параметрами після імпорту

Після того як дзвінки почали імпортуватись:

  1. Відкрийте розділ Діалоги.
  2. Натисніть кнопку фільтрів у верхній панелі.

Кнопка відкриття фільтрів у списку діалогів

У правій панелі будуть доступні:

  • стандартні фільтри (напрямок, оператор, клієнт, тривалість);
  • блок Властивості дзвінка з полями, які ви передали в properties.

Бічна панель фільтрації діалогів із додатковими властивостями дзвінка

Приклади практичного використання:

  • показати лише дзвінки відділу підтримки;
  • відфільтрувати звернення з певних міст;
  • відібрати тільки дзвінки з високим скорингом;
  • швидко порівнювати сегменти клієнтів у межах однієї команди.

Розширений troubleshooting: типові помилки і як діяти

401 Unauthorized

Що означає: не пройдена перевірка X-Secret-Key.

Що перевірити:

  • правильність ключа в налаштуваннях інтеграції;
  • правильність ключа на сервері;
  • чи не змінює заголовки ваш reverse proxy.

404 на /health, /contacts або /calls/:id/recording

Що означає: неправильний шлях або endpoint реально не існує на production.

Що перевірити:

  • базовий URL інтеграції;
  • роутинг на сервері;
  • доступність API з зовнішньої мережі.

Порожній імпорт у /calls

Що означає: дзвінки не проходять ваші або платформні фільтри.

Що перевірити:

  • коректність startTime/stopTime (секунди, а не мілісекунди);
  • коректність значень direction;
  • відповідність operatorId значенням із contacts.id;
  • чи не занадто жорсткі propertyFilters.

Властивості не відображаються у UI

Що означає: проблема у схемі або в ключах properties.

Що перевірити:

  • чи реалізовано /properties/schema;
  • чи збігаються key зі схеми і ключі в calls[].properties;
  • чи не повертаються null/складні об’єкти замість простих типів.

Запис не відкривається

Що означає: URL недоступний або протермінований.

Що перевірити:

  • доступність URL без внутрішньої VPN;
  • TTL тимчасового посилання;
  • правильний mimeType.

Фінальний чеклист перед запуском

  • Реалізовані endpoint-и: /health, /contacts, /calls, /calls/:id/recording.
  • Усі endpoint-и захищені перевіркою X-Secret-Key.
  • POST /calls повертає тільки завершені дзвінки.
  • properties передаються стабільно і в очікуваних типах.
  • За можливості реалізовано /properties/schema.
  • Ключі у схемі та у properties повністю узгоджені.
  • Створена команда і перевірений імпорт у розділі Діалоги.
  • Перевірена робота фільтрів за додатковими параметрами.

Коли всі пункти пройдені, інтеграція вважається production-ready: дані завантажуються стабільно, діалоги правильно сегментуються, а аналітика дає коректну картину по команді.