Как это работает
- Внешняя система отправляет
POSTс номером телефона и данными лида (один из двух режимов приёма — ниже) - Агентика проверяет лимиты, дедупликацию, окно расписания и схему полей сигнала
- Звонок проходит те же проверки, что и одиночный звонок через публичный API: слоты параллелизма, квота, контроль исходящих вызовов
- Каждая попытка фиксируется в журнале сигналов (принята, отложена, начата или отклонена — с кодом и причиной); как прошёл разговор, смотрите в запуске воркфлоу. При «занято»/«без ответа» планируются перезвоны
Два режима приёма сигнала
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}}):
{{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 по-прежнему принимается (старые интеграции не ломаются), но ни на что не влияет.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 (необязательно).
Дальше
- Кампании — массовый обзвон по списку контактов
- API-ключи — ключи для API-режима
- Рецепты n8n и Make — «Google Таблица / CRM → звонок» без кода
- Запуск агента одиночным звонком — разовый вызов без триггера