Исходящие вебхуки
Pleep вызывает ваш эндпоинт, когда лид меняет этап в воронке, сделка меняет стадию в вашей CRM или заканчивается звонок голосового агента
Исходящие вебхуки позволяют не опрашивать API, а получать события в момент, когда они происходят. Pleep отправляет POST с JSON на ваш HTTPS-эндпоинт и подписывает каждый запрос.
Это не то же самое, что входящие вебхуки
Входящие вебхуки — когда внешняя система вызывает Pleep, чтобы разбудить агента в диалоге. Здесь наоборот: Pleep вызывает вас.
Настройка
Откройте Настройки → Разработчикам, раздел «Исходящие вебхуки». Укажите название, HTTPS-адрес и события, на которые подписываетесь. После сохранения один раз показывается секрет — сохраните его, повторно он не отображается.
Там же видно последние 20 попыток доставки с кодом ответа, телом и текстом ошибки, а эндпоинт можно временно выключить или сменить ему секрет.
События
| Событие | Когда срабатывает |
|---|---|
lead.status_changed | Лид перешёл на другой этап воронки Pleep. Срабатывает независимо от того, кто его передвинул: AI-агент, оператор в интерфейсе, сценарий или вызов set-status |
crm.status_changed | Сделка или лид сменил стадию во внешней CRM: amoCRM или Битрикс24 |
call.completed | Закончился звонок голосового агента, входящий или исходящий. Приходит и тогда, когда человек не взял трубку |
crm.status_changed требует подключённой внешней CRM
Это событие транслирует изменения из amoCRM или Битрикс24. Если вы ведёте воронку внутри Pleep и внешняя CRM не подключена, событие не сработает никогда. Для воронки Pleep подписывайтесь на lead.status_changed.
Формат запроса
Заголовки
| Заголовок | Описание |
|---|---|
X-Pleep-Event | Имя события |
X-Pleep-Delivery | Идентификатор события, совпадает с полем id в теле |
X-Pleep-Timestamp | Время отправки, unix-секунды |
X-Pleep-Signature | Подпись вида sha256=<hex> |
Конверт
Тело любого события имеет одинаковую обёртку, полезная нагрузка лежит в data.
| Поле | Тип | Описание |
|---|---|---|
id | string | Идентификатор события. Не меняется между повторными попытками — используйте его для дедупликации |
event | string | Имя события |
occurred_at | string | Время события (ISO 8601) |
application_id | string | Идентификатор приложения |
data | object | Полезная нагрузка, зависит от события |
lead.status_changed
| Поле | Тип | Описание |
|---|---|---|
thread_id | string | Идентификатор диалога, тот же что в get-threads и в ссылке на диалог в мониторе |
phone | string | null | Номер клиента, если у канала он есть |
from | object | null | Этап, с которого ушёл лид. null, если этапа не было |
to | object | null | Новый этап. null, если этап сняли |
changed_by | string | null | Кто передвинул: AI, OPERATOR, SCENARIO, SYSTEM или VERIFIED_OUTCOME |
crm.status_changed
thread_id может быть null
Лид, заведённый прямо в CRM руками или импортом, ещё ни разу вам не писал, поэтому диалога у него нет. Это штатная ситуация, а не ошибка: в таком случае thread_id равен null, а единственная зацепка за человека — contact_phone. Обработчик должен это учитывать.
call.completed
Приходит после каждого звонка голосового агента. Исходящего: через make-call, из сценария или рассылки. И входящего. По телефону и в WhatsApp. Одно событие на звонок, в том числе когда человек не взял трубку, было занято или он сбросил вызов.
Звонок, на который не ответили:
| Поле | Тип | Описание |
|---|---|---|
thread_id | string | Диалог, в котором сохранился звонок. Тот же идентификатор, что в ответе make-call, в get-threads и в ссылке на диалог в мониторе |
assistant_id | string | Ассистент, которому принадлежит голосовой агент |
voice_agent_id | string | null | Голосовой агент, который звонил или принял звонок. Тот же, что voice_agent_id в make-call |
conversation_id | string | null | Идентификатор разговора. null, если звонок не соединился |
phone | string | null | Номер собеседника, только цифры, без + |
direction | string | outbound: звонил агент. inbound: звонили агенту |
status | string | Чем закончился звонок, значения ниже |
duration_seconds | number | Сколько длился разговор, в секундах. 0, если трубку не взяли |
started_at | string | Когда начался набор (исходящий) или агент ответил (входящий), ISO 8601 UTC. У исходящего с source: "other" это может быть момент, когда человек взял трубку |
ended_at | string | Когда звонок закончился, ISO 8601 UTC. Точность около секунды: это момент, когда конец звонка дошёл до Pleep |
end_reason | string | null | Что закончило звонок, если это известно, значения ниже. null у обычного разговора: кто положил трубку, линия не сообщает |
hangup_cause | number | null | Код причины от оператора связи, если звонок закончила сеть. Например, 17 занято, 19 нет ответа. Те же коды, что в make-call |
transcript | array | Разговор так, как он виден в мониторе: реплики { "role": "agent" | "caller", "text": "..." } по порядку. Пустой, если трубку не взяли |
source | string | Кто начал звонок, значения ниже |
status | Что значит |
|---|---|
answered | Человек взял трубку. Сюда же попадает звонок, который сбросили в первые секунды |
no_answer | Звонок шёл, но трубку не взяли |
busy | Занято |
rejected | Вызов отклонён. В WhatsApp это значит, что человек сбросил вызов. По телефону так же отвечает и сеть, когда не пропускает вызов, и телефон мог не зазвонить |
voicemail | Ответил автоответчик, агент молча положил трубку |
failed | Сеть не смогла соединить звонок, или линия отказалась его набирать |
Значения совпадают с call_status в ответе make-call: answered там называется connected, а not_connected и network_error здесь приходят как failed.
end_reason | Что значит |
|---|---|
voicemail | Агент положил трубку, услышав автоответчик |
ring_timeout | Никто не взял трубку, пока шли гудки |
line_hangup | Звонок закончила сеть до соединения, код в hangup_cause |
source | Кто начал звонок |
|---|---|
api | Запрос make-call, в том числе запланированный на потом через scheduled_at |
scenario | Действие «Позвонить» в сценарии |
broadcast | Рассылка, в том числе созданная через API |
inbound | Входящий звонок |
other | Остальное, например звонок, который AI-агент сделал из переписки или запланировал сам |
Как связать событие с запросом make-call
В ответе make-call приходит thread_id. Событие call.completed по этому звонку придёт с тем же thread_id и source: "api". conversation_id при звонке через телефонию Pleep известен только после соединения, поэтому для связи используйте thread_id.
Когда события нет
Когда Pleep не стал звонить (закончились минуты, человек отказался от звонков, нерабочее время, агент не настроен), звонка не было и call.completed не придёт. make-call в этом случае сразу отвечает ошибкой, а в итогах рассылки видна причина.
Если линия не сообщила вовремя, чем кончился набор (make-call ответил 504 CALL_RESULT_UNKNOWN), событие придёт, только если звонок соединился.
Если собственный номер агента подключён по-старому, через ElevenLabs, по звонку, где трубку не взяли, события не будет.
Проверка подписи
Подпись считается как HMAC-SHA256 от строки ${timestamp}.${тело}, где timestamp — значение заголовка X-Pleep-Timestamp, а тело — сырые байты запроса.
Проверяйте по сырому телу
Если распарсить JSON и собрать его обратно, порядок ключей и пробелы изменятся, и подпись не сойдётся. Это самая частая ошибка при подключении. Считайте подпись до разбора тела.
Время подписывается вместе с телом, поэтому перехваченный запрос нельзя переиграть позже: сверьте X-Pleep-Timestamp со своими часами и отклоняйте слишком старые.
Повторные попытки
На любой ответ вне диапазона 2xx, а также на таймаут или сетевую ошибку, Pleep повторит доставку. Всего до 5 попыток с нарастающей паузой. Таймаут одного запроса — 10 секунд.
Отвечайте 2xx сразу, а тяжёлую обработку уносите в свою очередь. Обработчик должен быть идемпотентным по полю id: при повторе оно то же самое.
Каждая попытка попадает в журнал доставок в интерфейсе, так что видно и код ответа, и тело ошибки с вашей стороны.
Требования к эндпоинту
- Только
https://. Адреса наhttp://,localhostи в приватных диапазонах отклоняются. - Адрес проверяется не только при сохранении, но и перед каждой отправкой.
- Выключенный эндпоинт не вызывается: попытка отмечается в журнале как
skipped_inactive.