Это главный эндпоинт клиентского API. Всё остальное — вспомогательное.
Идея: вы шлёте событие, а не текст
Привычный способ интеграции — «сформировать сообщение в коде и отправить». Он работает, но быстро становится обузой: текст напоминания живёт внутри вашего приложения, и каждая правка формулировки, каждая новая кнопка, каждое «а давайте ещё картинку» — это задача разработчику и деплой.
POST /{channel}/trigger переворачивает схему. Вы сообщаете боту: с этим человеком случилось вот такое событие, вот данные по нему. А что именно ему написать — решает сценарий в конструкторе.
Ваша система ЭТОЧАТБОТ
───────────── ──────────
«у этого человека → сценарий «Из другой автоматизации»
заканчивается тариф, ├─ сообщение с {{var.plan}}
plan=Премиум, days_left=3» ├─ кнопка «Продлить»
└─ ветка: если не ответил за сутки — напомнить
Что это даёт на практике:
- Текст меняет маркетолог, а не программист. Правка формулировки — это правка блока в конструкторе, код вы не трогаете.
- Логика тоже живёт в конструкторе. Кнопки, задержки, ветвление по ответу, передача оператору, запись в CRM — всё это блоки сценария. Ваш код об этом ничего не знает.
- Один вызов — любой канал. Сценарий отработает одинаково для Telegram, чата на сайте, ВКонтакте и МАКС.
- Данные передаются переменными. Название тарифа, число дней, сумма, номер заказа — всё это подставляется в текст в момент отправки.
Прежде чем начать: кому можно писать
Написать человеку можно только если он раньше сам обратился к боту. До первого обращения адреса доставки просто не существует — ни в одном канале.
Это ограничение самих мессенджеров, а не наше. Telegram позволяет писать только тем, кто хотя бы раз запускал бота; ВКонтакте требует от человека разрешения на сообщения от сообщества; в чате на сайте контакт появляется в момент первого сообщения.
Поэтому сценарий «выгрузим всю базу клиентов из CRM и разошлём напоминания» работать не будет. Работает другой: человек когда-то пришёл в бота, вы связали его с записью в своей системе (см. статью про привязку external_id), и дальше шлёте ему события.
Instagram-боты этот API не поддерживают вообще — вызов вернёт 400 unsupported_channel.
Шаг 1. Сценарий с триггером «Из другой автоматизации»
Кабинет → выберите бота → Автоматизации → создайте сценарий.
В качестве триггера выберите «Из другой автоматизации». Это тот самый вход, который открывает POST /{channel}/trigger.
Дальше соберите сценарий как обычно: блок сообщения, кнопки, задержки, условия — всё доступно.
Обязательно включите сценарий. Выключенный сценарий для API не существует: запрос вернёт 404 no_flow.
Шаг 2. Переменные в текстах
Всё, что вы передадите в поле vars, доступно в блоках сценария как {{var.имя}}.
Передали:
{"plan": "Премиум", "days_left": "3"}
Пишете в блоке сообщения:
Ваш тариф {{var.plan}} заканчивается через {{var.days_left}} дн.
Продлите сейчас — история диалогов и настройки сохранятся.
Человек получит: «Ваш тариф Премиум заканчивается через 3 дн.»
Переменные работают не только в текстах: их можно использовать в условиях сценария. Например, ветка «если {{var.days_left}} равно 1 — текст более настойчивый, кнопка одна и та же».
Ограничения на переменные
| Что | Ограничение |
|---|---|
| Имя переменной | латиница, цифры и подчёркивание (days_left — да, дней-осталось — нет) |
| Количество | до 20 штук за один вызов |
| Длина значения | до 500 символов |
| Тип значения | передавайте строками — так предсказуемее подстановка |
Числа лучше слать строкой ("3", а не 3): в текст они всё равно попадут как текст, а строка избавляет от сюрпризов с форматированием дробных.
Шаг 3. Вызов POST /{channel}/trigger
POST https://etochat.bot/api/v2/{channel}/trigger
Authorization: Bearer etchtbt_bot_<ваш ключ>
Content-Type: application/json
Тело:
{
"chat_id": "9995735656",
"vars": {
"plan": "Премиум",
"days_left": "3"
}
}
Вместо chat_id можно указать external_id — ваш собственный идентификатор пользователя, если вы предварительно привязали его через POST /{channel}/contacts/link. Это удобнее: в вашей системе не придётся хранить чужие идентификаторы.
Ответ:
{"triggered": true, "outcome": "handled", "chat_id": "9995735656"}
triggered: true означает, что подходящий сценарий найден и запущен. chat_id возвращается всегда — в том числе когда вы обращались по external_id, так что по ответу видно, кому именно ушло.
chat_idв ответах API всегда строка. Это не каприз: у чата на сайте он длиннее 16 цифр, и число в JavaScript теряет точность. Не приводите его к числу.
Пример 1. Тариф заканчивается через 3 дня
Здесь мы обращаемся по chat_id, который взяли из GET /{channel}/chats.
curl -X POST https://etochat.bot/api/v2/{channel}/trigger \
-H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{
"chat_id": "9995735656",
"vars": {
"event": "plan_expiring",
"plan": "Премиум",
"days_left": "3",
"expires_at": "24 июля"
}
}'
Обратите внимание на переменную event. Это удобный приём: один сценарий на все события, а внутри — условие по {{var.event}}, которое разводит ветки. Так вам не придётся плодить десяток сценариев и решать, какой из них дёрнуть.
Сообщение в конструкторе может выглядеть так:
Ваш тариф {{var.plan}} действует до {{var.expires_at}} — осталось {{var.days_left}} дн.
Продлите заранее, чтобы боты не остановились.
И кнопка-ссылка на оплату рядом с ним — её вы добавляете в конструкторе, в коде о ней знать не нужно.
Пример 2. Тариф активирован (PHP)
Здесь обращаемся по external_id — идентификатору пользователя в вашей системе. Предполагается, что вы один раз вызвали POST /{channel}/contacts/link и связали user-42 с его чатом.
<?php
function etochatTrigger(string $recipientExternalId, array $vars): array
{
$apiKey = getenv('ETOCHAT_API_KEY'); // etchtbt_bot_...
$payload = json_encode([
'external_id' => $recipientExternalId,
'vars' => $vars,
], JSON_UNESCAPED_UNICODE);
$ch = curl_init('https://etochat.bot/api/v2/{channel}/trigger');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
]);
$body = curl_exec($ch);
$code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false) {
return ['ok' => false, 'code' => 0, 'error' => 'network_error'];
}
$data = json_decode($body, true) ?: [];
if ($code >= 200 && $code < 300) {
return ['ok' => true, 'code' => $code, 'chat_id' => $data['chat_id'] ?? null];
}
return [
'ok' => false,
'code' => $code,
'error' => $data['error']['code'] ?? 'unknown',
'message' => $data['error']['message'] ?? '',
];
}
// Вызываем после успешной оплаты
$result = etochatTrigger('user-42', [
'event' => 'plan_activated',
'plan' => 'Премиум',
'valid_till' => '21 августа',
]);
if (!$result['ok']) {
// Пишем в лог, но не роняем оплату из-за недоставленного уведомления
error_log('etochat trigger failed: ' . $result['code'] . ' ' . $result['error']);
}
Ключевая мысль последнего блока: уведомление не должно ломать бизнес-операцию. Оплата прошла — значит, прошла, даже если человек успел заблокировать бота. Логируйте ошибку и идите дальше.
Ошибка no_flow: что она значит и что делать
{"error": {"code": "no_flow", "message": "…"}}
HTTP-статус — 404. Означает ровно одно: у этого бота нет включённого сценария с триггером «Из другой автоматизации». Запрос дошёл, ключ верный, человек найден — просто запускать нечего.
Проверьте по порядку:
- Сценарий вообще создан? Кабинет → бот → Автоматизации.
- В нём выбран триггер именно «Из другой автоматизации», а не «Ключевое слово», «Старт» и т. п.
- Сценарий включён. Черновик и выключенный сценарий для API не существуют.
- Вы используете ключ того самого бота. Ключ привязан к одному боту и не видит сценарии других ботов того же владельца — даже ваших собственных.
Четвёртый пункт — самая частая причина. Если ботов несколько, легко взять ключ не от того.
Остальные ошибки
Все ошибки приходят в одном формате: {"error":{"code":"…","message":"…"}} с осмысленным HTTP-статусом.
| Статус | Код | Что делать |
|---|---|---|
| 401 | unauthorized | Не передан заголовок Authorization |
| 401 | invalid_key | Ключ неверен, отозван или бот отключён — выпустите новый |
| 400 | unsupported_channel | Канал бота не поддерживается (Instagram) |
| 400 | recipient_required | Не указан ни chat_id, ни external_id |
| 400 | chat_id_invalid | Некорректный chat_id |
| 404 | contact_not_found | У этого бота нет такого контакта |
| 404 | external_id_not_linked | Сначала вызовите POST /{channel}/contacts/link |
| 404 | no_flow | Нет включённого сценария с нужным триггером (см. выше) |
| 402 | subscription_inactive | Подписка неактивна, отправка невозможна |
| 409 | recipient_blocked_bot | Человек остановил бота — это нормальная ситуация, не ошибка интеграции |
409 recipient_blocked_bot стоит обрабатывать отдельно: имеет смысл пометить у себя, что этому пользователю уведомления больше не доходят, и не долбить его каждый день.
Когда лучше POST /{channel}/messages, а когда /trigger
POST /{channel}/messages шлёт готовый текст, который вы собрали сами. Это уместно для сугубо технических вещей: одноразовый код, «ваш отчёт готов», строго форматированная выписка — то, что маркетолог никогда не будет переписывать.
POST /{channel}/trigger — для всего остального. Как только у уведомления появляется кнопка, ветвление, продолжение диалога или желание переформулировать текст — это сценарий.
Проверка перед запуском
GET /{channel}/me— ключ рабочий,can_send: true.GET /{channel}/chats— нашли себя в списке (напишите боту сами, если ещё не писали).- Вызвали
/triggerсо своимchat_idи тестовыми переменными — сообщение пришло, подстановки на месте. - Только после этого подключаете реальные события своей системы.
Дальше логично посмотреть статью про привязку external_id — она избавляет вас от необходимости хранить chat_id на своей стороне и делает вызовы /trigger заметно чище.