惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
G
Google Developers Blog
爱范儿
爱范儿
博客园 - 司徒正美
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
J
Java Code Geeks
The Cloudflare Blog
M
MIT News - Artificial intelligence
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
博客园 - Franky
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Blog — PlanetScale
Blog — PlanetScale
Vercel News
Vercel News
宝玉的分享
宝玉的分享
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
B
Blog
小众软件
小众软件
Microsoft Azure Blog
Microsoft Azure Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
WordPress大学
WordPress大学
T
Troy Hunt's Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
H
Hacker News: Front Page
H
Help Net Security
S
Security @ Cisco Blogs
V
V2EX
Security Archives - TechRepublic
Security Archives - TechRepublic
Stack Overflow Blog
Stack Overflow Blog
O
OpenAI News
L
LINUX DO - 最新话题
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Secure Thoughts
Help Net Security
Help Net Security
F
Full Disclosure
博客园 - 叶小钗
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Jina AI
Jina AI
K
Kaspersky official blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
P
Privacy International News Feed
Scott Helme
Scott Helme

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Защищаем личные номера телефонов на маркетплейсах: соединяем клиента и исполнителя
Katner (Exol · 2026-04-30 · via Все публикации подряд на Хабре

Защищаем личные номера телефонов на маркетплейсах: соединяем клиента и исполнителя

Уровень сложностиСредний

Время на прочтение9 мин

Охват и читатели2.1K

Туториал

Если вы развиваете маркетплейс или сервис, где клиент общается с выездными специалистами, то важно удерживать звонки внутри платформы. Когда мастер и клиент уходят в прямой контакт, компания теряет комиссию, историю общения и повторные продажи.

Полностью избежать обмена номерами невозможно, при встрече исполнитель и клиент могут договориться напрямую. Но если клиент не хочет оставлять личный номер исполнителям и предпочитает гарантии и возможности вашей платформы, такой сценарий помогает сохранять контакт защищённым.

В этом материале соберём такой сценарий на Python, используя вместо базы данных Битрикс24. Решение берёт контекст из CRM в момент звонка и через МТС Exolve соединяет клиента, исполнителя или поддержку. 

Общая схема работы

В этом сценарии единый промежуточный номер становится публичной точкой входа для звонка. Исполнитель звонит клиенту через этот номер. Когда клиент перезванивает, приложение смотрит текущую сделку в Битрикс24 и выбирает адресата. Так коммуникация остаётся привязанной к заказу и ответственному исполнителю.

  1. Исполнитель входит в личный кабинет по коду из СМС. Затем выбирает заказ и нажимает кнопку «Связаться с клиентом». Далее сервис собирает его рабочий контекст из Битрикс24: активные сделки, адрес и номер клиента

  2. Когда контекст собран, МТС Exolve через Callback API звонит клиенту и исполнителю, и затем соединяет их через промежуточный номер

Стек: Python 3.10+, Streamlit, Flask, Битрикс24 REST API, SMS API и Callback API МТС Exolve.

Архитектура решения

У сервиса нет своей базы данных и очереди. В Streamlit хранится краткоживущее состояние авторизации, а рабочее состояние заказов остаётся в Битрикс24. За счёт этого приложение связывает CRM и телефонию и не дублирует бизнес-данные.

Кабинет исполнителя собран в app.py и authservice.py. App.py отвечает за интерфейс, состояние сессии и действия пользователя. Authservice.py генерирует одноразовый код и отправляет его исполнителю через SMS API. После проверки кода пользователь входит в личный кабинет с заказами.

Интеграция с Битрикс24 и МТС Exolve вынесена в bitrix_integration.py, auth_service.py и exolve_voice.py. Первый модуль читает и изменяет данные Битрикс24. Auth_service.py отвечает за аутентификацию исполнителя по СМС. Exolve_voice.py запускает колбэк-звонки через промежуточный номер. Битрикс24 хранит контекст и заказчика, а МТС Exolve даёт каналы с СМС и звонком.

Входящие звонки обрабатывает webhook_router.py. Модуль принимает вебхук от телефонии, ищет активный контекст клиента и возвращает JSON с адресатом звонка. В config.py собраны ключи и номера.

Пререквизит

Проекту нужны только Python-зависимости и переменные окружения для МТС Exolve и Битрикс24. Здесь запускаются два процесса: кабинет исполнителя на Streamlit и сервер для приёма вебхуков на Flask. Первый обслуживает действия пользователя, второй принимает входящие события от телефонии.

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Минимальный набор переменных:

EXOLVE_API_KEY=***
AUTH_POOL_NUMBER=7999XXXXXXX
SINGLE_SERVICE_NUMBER=7800XXXXXXX
SUPPORT_NUMBER=7800YYYYYYY
BITRIX_WEBHOOK=https://your-domain.bitrix24.ru/rest/...

Для локального теста достаточно поднять оба процесса и отправить вебхук вручную. Для реального входящего трафика Flask должен быть доступен извне.

Шаг 1. Авторизуем исполнителя

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

# auth_service.py

import requests
from config import Config

def send_flash_call(target_phone: str):
   auth_number = Config.AUTH_POOL_NUMBER


   url = "https://api.exolve.ru/voice/v1/MakeCall"
   headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"}


   payload = {
       "number": auth_number,
       "destination": target_phone,
       "record": False,
       "time_limit": 5,
   }


   try:
       resp = requests.post(url, headers=headers, json=payload, timeout=5)
       resp.raise_for_status()
       return auth_number[-4:]
   except Exception as e:
       print(f"Ошибка Flash Call: {e}")
       return None

Дальше интерфейс сохраняет сгенерированный код в короткоживущем состоянии и сравнивает его с тем, который пользователь ввёл в форму.

# app.py

if not st.session_state.auth:
   phone = st.text_input("Ваш телефон", placeholder="79990000000")


   if st.button("Получить код доступа"):
       with st.spinner("Звоним..."):
           code = send_flash_call(phone)
           if code:
               st.session_state.verification = code
               st.success("Ждите звонка-сброса. Введите последние 4 цифры.")


   user_code = st.text_input("Код из звонка")
   if st.button("Войти"):
       if user_code == st.session_state.get("verification"):
           st.session_state.auth = True
           st.session_state.phone = phone
           st.rerun()

После проверки кода кабинет сохраняет номер исполнителя в сессии и дальше использует его как ключ для поиска активных сделок в Битрикс24. Отдельная учётная запись здесь не нужна: один и тот же номер работает и как способ входа, и как идентификатор сотрудника для CRM.

Шаг 2. Собираем рабочий контекст исполнителя из Битрикс24

После входа кабинет собирает рабочий контекст на лету из CRM. Сначала ищем в Битрикс24 исполнителя по мобильному номеру. Затем запрашиваем все активные сделки, где этот пользователь назначен ответственным. После этого для каждой сделки добираем контакт клиента и его телефон, чтобы из этих данных собрать карточки в интерфейсе.

# bitrix_integration.py

def get_user_id_by_phone(phone: str):
   method = "user.search"
   params = {"FILTER": {"PERSONAL_MOBILE": phone}}

   resp = requests.post(f"{Config.BITRIX_WEBHOOK}/{method}", json=params, timeout=10)
   resp.raise_for_status()
   result = resp.json().get("result", [])
   return result[0]["ID"] if result else None

def get_active_deals(master_phone: str):
   user_id = get_user_id_by_phone(master_phone)
   if not user_id:
       return []

   params = {
       "filter": {
           "ASSIGNED_BY_ID": user_id,
           "!STAGE_ID": FINAL_STAGES,
       },
       "select": ["ID", "TITLE", "UF_CRM_ADDRESS", "CONTACT_ID"],
   }

   resp = requests.post(f"{Config.BITRIX_WEBHOOK}/crm.deal.list", json=params, timeout=10)
   resp.raise_for_status()

   deals = []
   for item in resp.json().get("result", []):
       contact_id = item.get("CONTACT_ID")
       client_phone = _get_contact_phone(contact_id) if contact_id else None


       if client_phone:
           deals.append(
               {
                   "id": item["ID"],
                   "address": item.get("UF_CRM_ADDRESS", "Адрес не указан"),
                   "title": item["TITLE"],
                   "client_phone_hidden": client_phone,
               }
           )

   return deals

Здесь user_id — это идентификатор исполнителя в Битрикс24, полученный по его номеру телефона. Константа FINAL_STAGES — это список финальных стадий сделки, которые мы исключаем из выборки, чтобы в кабинет попадали только активные заказы.

На выходе сервис получает готовые карточки со сделкой, адресом и телефоном клиента. Поле client_phone_hidden хранит исходный номер клиента из CRM, а не промежуточный номер: подмена происходит только в момент звонка через Callback API МТС Exolve.

Шаг 3. Звоним через единый номер

Когда исполнитель нажимает «Связаться», приложение вызывает метод MakeCallback из API МТС Exolve и передаёт в него три значения: номер сервиса, идентификатор колбэк-ресурса и два плеча вызова: line_1 и line_2. Дальше разговор собирается уже на стороне телефонии.

# exolve_voice.py

import requests
from config import Config

def initiate_masked_call(master_phone: str, client_phone: str):
   url = "https://api.exolve.ru/voice/v1/Callback"
   headers = {"Authorization": f"Bearer {Config.EXOLVE_API_KEY}"}


   payload = {
       "number": Config.SINGLE_SERVICE_NUMBER,
       "destination": master_phone,
       "peer": client_phone,
       "record": True,
   }

   try:
       requests.post(url, headers=headers, json=payload, timeout=5)
   except Exception as e:
       print(f"Callback error: {e}")

Кнопка в кабинете, которая запускает этот вызов.

# app.py

if c1.button("📞 Связаться", key=f"call_{deal['id']}"):
   initiate_masked_call(
       master_phone=st.session_state.phone,
       client_phone=deal["client_phone_hidden"],
   )
   st.toast("Соединяем... Ждите входящий.")

Шаг 4. Принимаем входящий вебхук и ищем активный контекст клиента

Обратный звонок клиента запускает второй сценарий: теперь нужно понять, кому именно переводить вызов. При входящем звонке МТС Exolve отправляет на наш сервер JSON-RPC запрос с методом getControlCallFollowMe. Из параметра params.numberA сервис берёт номер вызывающего абонента, ищет по нему контакт в Битрикс24 и проверяет активные сделки.

# webhook_router.py

@app.route("/exolve/incoming", methods=["POST"])
def handle_call():
   data = request.json or {}
   caller_phone = data.get("numbers", {}).get("a")


   master_phone = find_master_phone_by_client(caller_phone)

Находим контакт клиента по номеру телефона.

def find_master_phone_by_client(client_phone: str):
   params = {
       "type": "PHONE",
       "values": [client_phone],
       "entity_type": "CONTACT",
   }
   resp = requests.post(
       f"{Config.BITRIX_WEBHOOK}/crm.duplicate.findbycomm",
       json=params,
       timeout=10,
   )
   resp.raise_for_status()
   contacts = resp.json().get("result", {})


   if not contacts:
       return None


   contact_id = contacts[0]

Точка входа здесь одна: номер клиента из numbers.a. По этому номеру сервис поднимает контакт и рабочий контекст клиента в CRM.

Шаг 5. Маршрутизируем звонок исполнителю или в поддержку

Когда контакт найден, сервис ищет первую активную сделку этого клиента, берёт из неё ответственного и по его идентификатору получает мобильный номер исполнителя. Если такая цепочка собирается целиком, в ответ на вебхук уходит команда перевести звонок этому человеку. Если активной сделки нет или номер ответственного не найден, вызов уходит на номер поддержки.

# webhook_router.py

   deal_params = {
       "filter": {
           "CONTACT_ID": contact_id,
           "!STAGE_ID": FINAL_STAGES,
       },
       "select": ["ASSIGNED_BY_ID"],
   }
   deal_resp = requests.post(
       f"{Config.BITRIX_WEBHOOK}/crm.deal.list",
       json=deal_params,
       timeout=10,
   )
   deal_resp.raise_for_status()
   deals = deal_resp.json().get("result", [])


   if not deals:
       return None

   assigned_id = deals[0]["ASSIGNED_BY_ID"]
   user_resp = requests.post(
       f"{Config.BITRIX_WEBHOOK}/user.get",
       json={"ID": assigned_id},
       timeout=10,
   )

Находим мобильный номер ответственного.

   user_resp.raise_for_status()

   users = user_resp.json().get("result", [])
   if users:
       return users[0].get("PERSONAL_MOBILE")

   return None

Возвращаем команду на перевод звонка или уводим на резервный маршрут.

   redirect_number = master_phone if master_phone else Config.SUPPORT_NUMBER

   return jsonify({
       "id": req_id,
       "jsonrpc": "2.0",
       "sip_id": sip_id,
       "result": {
           "redirect_type": 1,
           "followme_struct": [
               1,
               [
                   {
                       "I_FOLLOW_ORDER": 1,
                       "ACTIVE": True,
                       "NAME": "Assigned master",
                       "REDIRECT_NUMBER": redirect_number,
                       "PERIOD": "always",
                       "PERIOD_DESCRIPTION": "always",
                       "TIMEOUT": 30
                   }
               ]
           ]
       }
   })

На этом этапе Битрикс24 — источник истины для маршрутизации. Не телефония решает, кому звонить, а текущая сделка в CRM. Это удобно как MVP, потому что не нужна своя база соответствий между клиентами и сотрудниками.

Шаг 6. Возвращаем изменение статуса обратно в CRM

После визита исполнитель может закрыть заказ из того же кабинета. Здесь сервис уже записывает в CRM результат выполнения. Так карточка сделки и реальный статус выезда сохраняются сразу после звонка или визита.

# bitrix_integration.py

def closedeal(dealid: str):
   params = {"id": dealid, "fields": {"STAGEID": "WON"}}
   requests.post(f"{Config.BITRIX_WEBHOOK}/crm.deal.update", json=params, timeout=10)

Кнопка в кабинете, которая отправляет это обновление.

# app.py

if c2.button("✅ Выполнил", key=f"done_{deal['id']}"):
   close_deal(deal["id"])
   st.success("Заказ закрыт!")
   st.rerun()

Здесь процесс короткий: интерфейс передаёт идентификатор сделки, а Битрикс24 получает новую стадию WON.

Запуск и проверка

Запускаем сначала сервер для приёма вебхуков, затем интерфейс исполнителя. В отдельных терминалах это выглядит так:

python webhook_router.py
streamlit run app.py

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

После этого можно проверить входящий маршрут вручную. Если в SUPPORT_NUMBER задан тестовый номер, а Битрикс24 не найдёт клиентский номер как активный контакт, сервис должен отдать резервный маршрут:

curl -X POST "http://127.0.0.1:5000/exolve/incoming" \
 -H "Content-Type: application/json" \
 -d '{
   "id": 1,
   "jsonrpc": "2.0",
   "method": "getControlCallFollowMe",
   "params": {
     "sip_id": "7800XXXXXXX",
     "numberA": "79990000000"
   }
 }'

Ожидаемый ответ в минимальном сценарии:

{
 "id": 1,
 "jsonrpc": "2.0",
 "sip_id": "7800XXXXXXX",
 "result": {
   "followme_struct": [
     1,
     [
       {
         "ACTIVE": true,
         "I_FOLLOW_ORDER": 1,
         "NAME": "Assigned master",
         "PERIOD": "always",
         "PERIOD_DESCRIPTION": "always",
         "REDIRECT_NUMBER": "7800YYYYYYY",
         "TIMEOUT": 30
       }
     ]
   ],
   "redirect_type": 1
 }
}

Дальше проверьте основной сценарий: зайдите в Streamlit под номером исполнителя, убедитесь, что подтянулись активные сделки, нажмите Связаться, а затем вручную отправьте вебхук с номером клиента из CRM. Если получили 4xx или пустой маршрут, сначала посмотрите payload и данные в Битрикс24. Если 5xx, проверьте сетевые таймауты, доступность REST-вебхука и корректность номеров в карточках исполнителей и клиентов.

Возможности для развития

  1. Привести номера телефонов к единому формату на входе и выходе. Без этого поиск по Битрикс24 быстро начинает расходиться на разных форматах записи

  2. Добавить подпись или иной способ валидации входящего вебхука. Сейчас эндпоинт доверяет любому POST с подходящим JSON

  3. Вынести коды авторизации из session_state в серверное TTL-хранилище, добавить ограничение частоты отправки СМС и журнал попыток входа

  4. Добавить реатраи с нарастающей задержкой и сквозной идентификатор события для запросов в МТС Exolve и Битрикс24, чтобы видеть, где именно рвётся цепочка

  5. Проверять ответы Битрикс24 и МТС Exolve, логировать сбои и показывать их в интерфейсе, чтобы закрытие заказа или запуск звонка не выглядели успешными только на экране

  6. Определить детерминированные правила маршрутизации для нескольких активных сделок одного клиента вместо правила брать первую

  7. Подготовить наблюдаемость: структурированные логи, алерты по ошибкам интеграций и простой аудит событий по номеру, сделке и сотруднику

В итоге

Получился легковесный сценарий без своей БД. Он работает как связка Битрикс24 и МТС Exolve, где CRM хранит рабочий контекст, а телефония собирает сам разговор.

Для MVP это простой способ быстро запустить безопасный сервис с одним промежуточным номером и не раскрывать личные номера исполнителя и клиента, а вход сотрудников организовать через код в СМС без отдельной учётной системы.

Дальше такой сценарий можно развивать через голосового робота МТС Exolve: с IVR и генерацией речи можно будет уточнять нужный заказ и точнее маршрутизировать звонки, когда у клиента или исполнителя одновременно открыто несколько заказов, при этом сохраняя один и тот же промежуточный номер.