Звонок по сигналу — это настроенная привязка «входящий сигнал → исходящий звонок». Ваша CRM, бэкенд или сервис автоматизации отправляет один HTTP-запрос — Агентика немедленно звонит указанному лиду и подключает голосового агента. В отличие от кампании, которая обзванивает заранее загруженный список, сигнал обслуживает «горячие» лиды по одному: пришёл лид — ушёл звонок, без очередей и пакетной обработки. Триггеры настраиваются в разделе Звонок по сигналу. В одной форме собрано всё: воркфлоу и конфигурация телефонии, лимиты, расписание звонков, ретраи, поля сигнала и источник сигнала. В карточке триггера — журнал принятых сигналов, цепочки перезвонов и запуски воркфлоу.

Как это работает

  1. Внешняя система отправляет POST с номером телефона и данными лида (один из двух режимов приёма — ниже)
  2. Агентика проверяет лимиты, дедупликацию, окно расписания и схему полей сигнала
  3. Звонок проходит те же проверки, что и одиночный звонок через публичный API: слоты параллелизма, квота, контроль исходящих вызовов
  4. Каждая попытка фиксируется в журнале сигналов (принята, отложена, начата или отклонена — с кодом и причиной); как прошёл разговор, смотрите в запуске воркфлоу. При «занято»/«без ответа» планируются перезвоны

Два режима приёма сигнала

API-режим

Отправитель использует API-ключ организации — тот же X-API-Key, что и для остального API Агентики (см. API-ключи). В адресе — публичный идентификатор триггера signal_uuid (он виден в карточке триггера и не является секретом):

Webhook-режим

Для систем, у которых нет API-ключа Агентики: токен триггера сам является секретом и передаётся прямо в пути URL. Заголовки авторизации не нужны:
Токен webhook-режима показывается один раз — при создании триггера или после генерации нового («Сгенерировать новый токен» в карточке). Скопируйте его сразу: в базе хранится только хеш, восстановить его невозможно. Если токен скомпрометирован — сгенерируйте новый, старый перестаёт работать мгновенно.
Оба режима проходят один и тот же обработчик и возвращают одинаковые коды ответов.

Контракт сигнала

Тело запроса — JSON. Каркас фиксированный: Дополнительные поля описываются на триггере в блоке Поля сигнала (payload_schema): вы задаёте имя, JSON-тип (string, number, boolean, object, array) и обязательность каждого параметра, а также сопоставление «поле сигнала → ключ контекста» с шаблонами вида {{field}} или {{lead.contact.name}} (путь через точку во вложенный JSON; числовой сегмент — индекс массива, {{lead.phones.0}}):
Тогда сигнал с дополнительными полями выглядит так:
Шаблон из одного плейсхолдера сохраняет исходный JSON-тип значения (число останется числом); составной шаблон интерполируется как строка. Отсутствующее конечное поле даёт пустую строку, но если путь не разрешается раньше конца ({{lead.contact.name}}, а в сигнале нет lead или lead.contact, либо это не объект), сигнал отклоняется сразу — 422: звонок с молча пустой переменной сценария хуже отказа. Ключи из context_mapping приоритетнее одинаковых ключей свободного context.
Служебные ключи контекста (phone_number, signal_trigger_id, external_id и другие) устанавливаются сервером и перетирают присланные — подменить атрибуцию звонка через тело сигнала невозможно.
Успешный ответ — 202:
По workflow_run_id можно позже получить сведения о запуске, запись и транскрипт. Если сигнал пришёл вне окна звонков, а у триггера выбрано «Отложить» (см. «Окна звонков»), ответ тоже 202, но звонок запланирован:

Дедупликация

У триггера настраивается окно дедупликации dedup_window_seconds (1–86400 секунд; у новых триггеров по умолчанию 300 секунд; пусто — без дедупликации). Повторный сигнал с тем же external_id, чья предыдущая принятая запись моложе окна, не создаёт второй звонок — Агентика отвечает 200:
  • Шлите external_id — ваши повторы безопасны в окне: повторные отправки на вашей стороне (таймауты, рестарты) не приведут к двойному звонку клиенту.
  • После истечения окна тот же external_id означает новый звонок — «лид вернулся».
  • Сигнал без external_id у триггеров с защитными настройками v2 (все новые триггеры; у старых — после «Применить безопасные настройки») дедуплицируется по номеру телефона (в нормализованном виде E.164) — в пределах окна, но не дольше 5 минут, даже если окно триггера длиннее: через 5 минут тот же номер снова получит звонок. Старые триггеры без этих настроек по номеру не дедуплицируют — как и раньше. Тестовые сигналы из карточки триггера в дедупликации по номеру не участвуют.
  • Тот же external_id с другим номером — не дубликат: номер лида изменился, Агентика примет сигнал как новый (и отметит это в своих логах), а не выбросит его молча.
  • Триггер без окна дедупликации не дедуплицирует ничего: каждый POST — отдельный звонок.
Дедупликация учитывает только записи, которые ещё могут привести к звонку (принятые, запланированные, начатые). Отклонённые и упавшие записи окно не блокируют: получив отказ и устранив его причину, можно спокойно присылать тот же external_id — будет предпринята новая попытка.Статус received в ответе на дубликат означает «сигнал ещё в обработке»; запись со статусом received старше ~10 минут — это зависшая первая попытка, и повтор после этого окна создаст новый звонок.

Окна звонков

Блок Расписание звонков (часовой пояс + слоты, как у кампаний) разрешает звонить только в заданные интервалы. Время задаётся строго как ЧЧ:ММ; конец слота может быть 24:00 — до полуночи. Слот, у которого конец раньше начала (22:00–02:00), идёт через полночь в следующие сутки. Время считается по часам выбранного часового пояса, переходы на летнее/зимнее время учитываются: если начало слота попадает в «пропущенный» час перевода часов вперёд, слот открывается в момент перевода. Перезвоны тоже планируются только внутрь окна. Что делать с сигналом вне окна, решает настройка «Сигнал вне окна»:
  • Отложить до открытия окна (по умолчанию у новых триггеров, out_of_window_policy: "defer") — сигнал принимается (202, "status": "scheduled", в scheduled_for — время открытия окна) и Агентика сама позвонит, когда окно откроется. Поля сигнала и номер проверяются сразу: невалидный сигнал получит 422, а номер вне белого списка направлений — 403 сейчас, а не упадёт ночью. Повтор сигнала в окне дедупликации ответит 200 с тем же scheduled_for.
  • Отклонить ("reject", поведение триггеров, созданных до появления настройки) — ответ 423 Locked с заголовком Retry-After, который подсказывает, через сколько секунд окно откроется; повторять сигнал должен отправитель.
Отложенный запуск включается на инстансе флагом SIGNAL_DEFER_ENABLED (для отдельной организации — через её настройки). Пока флаг выключен, триггеры с «Отложить» отвечают 423, как «Отклонить», а уже отложенные сигналы не набираются: они ждут включения, перепроверяя его каждые 15 минут, и через 6 часов после запланированного времени помечаются неуспешными (expired). Если окно не может открыться вообще (например, в расписании указан несуществующий часовой пояс), звонков по триггеру нет — сигнал получает 423.

Перезвоны

Если звонок закончился «занято» или «без ответа», Агентика может перезвонить автоматически — по настройкам блока Ретраи (семантика как у кампаний): число повторов max_retries (0–10), пауза retry_delay_seconds (30–3600) и то, какие исходы повторять (retry_on_busy, retry_on_no_answer). По умолчанию: 1 повтор по «занято» и «без ответа» через 300 секунд у новых триггеров (больше минимального интервала между звонками на один номер — 240 секунд, который действует и на перезвоны) и через 120 секунд у старых триггеров без защитных настроек v2.
Повтор по голосовой почте для сигналов не поддерживается: распознавание автоответчика пока не передаёт этот исход в телефонные статусы. Поле retry_on_voicemail в retry_config по-прежнему принимается (старые интеграции не ломаются), но ни на что не влияет.
Политика ретраев v2 (только у триггеров с защитными настройками v2): вместо полей v1 в retry_config можно передать {"max_attempts": 3, "intervals_seconds": [300, 3600], "on": ["busy", "no_answer"], "stop_on": ["confirmed", "rescheduled", "declined", "dnc", "cancelled_at_source"], "voicemail_action": "ignore"}. max_attempts считает все попытки вместе с первой; в intervals_seconds должно быть ровно max_attempts − 1 пауз (иначе 422). В on кроме busy/no_answer можно перечислить исход разговора (например, no_contact) — такой состоявшийся звонок тоже повторится. Исход из stop_on («стоп по успеху») завершает цепочку: после confirmed перезвонов нет, даже если линия затем вернула «занято». Старые настройки max_retries/retry_delay_seconds читаются как max_attempts = max_retries + 1 с одинаковыми паузами. Каждый перезвон учитывается в лимите попыток на номер. Перезвон планируется только в окно расписания и проходит те же проверки, что и первый звонок. HTTP-ответ при этом один — на исходный сигнал; о перезвонах узнавайте из журнала сигналов в карточке триггера (время, external_id, статус, код, число попыток, запуск воркфлоу), а об итоге разговора — из самого запуска воркфлоу (вкладка запусков триггера или API запусков). Если у триггера включён result webhook и на инстансе включён SIGNAL_RESULT_WEBHOOK_ENABLED, итог последней попытки приходит туда же подписанным событием. Перезвон получает тот же context и те же значения context_mapping, что и исходный сигнал. Контекст хранится в журнале зашифрованным и удаляется через 7 дней после завершения обработки записи (сама запись журнала остаётся).

Стоп-лист (DNC)

У каждой организации есть стоп-лист номеров «не звонить» (/api/v1/dnc). Номер из стоп-листа не набирается ни по сигналу, ни перезвоном, ни кампанией, ни через POST /api/v1/public/agent/... (там — 409): приёмник отвечает 200 {"status": "skipped", "skip_reason": "dnc"} (не ошибка — повторять не нужно), а уже запланированный звонок на такой номер пропускается в момент набора. Добавить номер может любой участник организации (POST /api/v1/dnc), агент в разговоре — встроенным инструментом add_to_dnc (тип инструмента native, config.action: "add_to_dnc"; блокирует номер текущего телефонного звонка; в веб-звонках и виджете не работает), снять — только владелец организации с указанием причины (PATCH /api/v1/dnc/{id}/release). Если стоп-лист временно недоступен, звонок не совершается (503, повторите позже). Основание для звонков указывается в триггере: consent_basis (service_notification, existing_relationship, explicit_consent, unspecified — по умолчанию) и consent_source (откуда оно — например, пункт договора). В журнал каждого набранного звонка записывается снимок основания на момент звонка.

Отмена звонка

Запланированный звонок (статус scheduled, в том числе ожидающий перезвон) можно отменить:
  • webhook-режим: DELETE /api/v1/public/signals/t/{token}/requests?external_id=… (или idempotency_key=…, или source_ref=… — ровно один параметр);
  • API-режим: DELETE /api/v1/public/signals/{signal_uuid}/requests/{request_id} с заголовком X-API-Key;
  • из интерфейса — кнопка «Отменить» в журнале сигналов.
Ответ 200 {"cancelled": n, "cancel_pending": 0} — сколько запланированных записей отменено; 202 {"cancelled": 0, "cancel_pending": n} — звонок уже взят в работу, но ещё не набран: он будет отменён до набора (итог cancelled придёт в result webhook); 409 — звонок уже совершён; 404 — триггер или запрос не найден. Выключенный триггер не набирает свои запланированные звонки: они ждут включения (перепроверка раз в 15 минут) и отменяются, если триггер не включили за expire_after_seconds. Удалённый триггер отменяет все свои запланированные звонки; журнал сохраняется.

Коды ответов

Политика повторов отправителя: повторяйте при 423 / 429 / 503 (по Retry-After, где есть заголовок); после 402 — пополните баланс и повторите сигнал; остальные 4xx не повторяйте — исправьте запрос или настройки триггера. Если вы шлёте external_id, повтор «вслепую» не создаст двойного звонка в окне дедупликации — но и не запустит новый звонок после отказа.

Лимиты

IP-лимит приёмника атрибутирует запросы по клиентскому адресу из X-Forwarded-For, но доверяет этому заголовку только от известных прокси: по умолчанию это приватные RFC1918-сети (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 — переменная окружения PUBLIC_SIGNALS_TRUSTED_PROXIES, пустое значение полностью отключает доверие к заголовку). Для нестандартных топологий (публичный внешний прокси, CDN перед инстансом) скорректируйте список на инстансе — иначе все клиенты будут считаться одним IP. Для OSS-инсталляций, где приёмник доступен из интернета напрямую (docker-compose без ingress-прокси), задайте PUBLIC_SIGNALS_TRUSTED_PROXIES="" — тогда лимит считается по реальному адресу соединения.

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

  • Токен webhook-режима — секрет: показывается один раз, в базе хранится только хеш, в логи и ответы не попадает. Компрометация — сразу «Сгенерировать новый токен»: старый инвалидируется мгновенно, восстановления нет.
  • Секрет в URL — остаточный риск: URL может утекать в логи прокси на стороне отправителя. Передавайте сигнал только по HTTPS и не логируйте полный URL; опциональная HMAC-подпись запроса планируется в следующей версии API. На стороне Агентики токен в журналы доступа не пишется: API-логи не содержат путей запросов, nginx OSS-инсталляции маскирует токен (/api/v1/public/signals/t/***), а в Kubernetes для этого пути журнал доступа ingress отключён. Журнал ошибок ingress-nginx (таймауты и обрывы соединения с бэкендом) по-прежнему пишет строку запроса вместе с токеном — обращайтесь с ним как с журналом, содержащим секреты.
  • Единый 404 для несуществующих, выключенных и чужих триггеров — перечислить чужие триггеры перебором нельзя; лимит запросов с одного IP дополнительно затрудняет подбор токена.
  • Подмена атрибуции невозможна: организация выводится из секрета (ключа или токена), а не из тела запроса; серверные ключи контекста перетирают присланные.

Изменения поведения (signal calls v2)

  • Пути шаблонов context_mapping: неразрешимый префикс пути ({{lead.contact.name}} без lead или lead.contact) теперь отклоняет сигнал 422; раньше переменная молча оставалась пустой. Проверьте шаблоны, если отправитель шлёт неполные объекты.
  • Дедупликация по номеру (сигнал без external_id) работает только у триггеров с защитными настройками v2 и не дольше 5 минут; у старых триггеров поведение не изменилось.
  • Поля v2 в теле сигнала (idempotency_key, source_ref/purpose, scheduled_at, local_time, timezone, call_at) действуют только у триггеров с защитными настройками v2; у старых триггеров это обычные пользовательские поля, как и раньше. Заголовок Idempotency-Key работает у всех триггеров.
  • Ответ, после которого нужно повторить (423, 429, 5xx), освобождает ключ идемпотентности: повтор с тем же ключом будет новым сигналом, а не дубликатом отказа. Повтор, пока идёт автоматический перезвон, возвращает состояние последней попытки.
  • Отложенные сигналы с номером вне белого списка направлений отклоняются сразу (403), а не принимаются как scheduled.
  • Конец слота 24:00 снова принимается и означает «до полуночи».
  • Права: создавать, менять (включая consent_basis, лимиты, service_call), удалять триггеры, перевыпускать токен, отправлять тестовый сигнал и отменять звонки может только владелец организации; участники видят триггеры и журнал. Все изменения настроек записываются в журнал аудита (секреты маскируются).
  • Выключенный триггер больше не проваливает свои запланированные звонки сразу, а придерживает их до expire_after_seconds; удаление триггера мягкое — журнал и снимки согласий сохраняются.

Тестовый сигнал

Кнопка «Отправить тестовый сигнал» в карточке триггера делает настоящий звонок на указанный номер через тот же воркфлоу и те же проверки, но без лимитов частоты и окна расписания. Можно передать и context (JSON-объект) — так тестовый звонок проверяет переменные сценария, например {"client_name": "Анна", "service": "Стрижка"}. Через API: POST /api/v1/signal-triggers/{id}/test-signal с полями phone_number, external_id (необязательно) и context (необязательно).

Дальше