Отправка сообщения через API в Ватсапе, Телеграме и Максе одному клиенту: один запрос, свой текст и вложение, без рассылки. Для кодов и напоминаний.

Есть сообщения, которые нельзя откладывать до вечерней рассылки. Клиент оплатил заказ. Записался на визит. Попросил код подтверждения по телефону, пока оператор держит трубку.
Такое сообщение адресовано одному человеку, живёт несколько минут и никогда не повторится. Собирать под него рассылку странно: аудитория из одного номера, текст под один случай, и всё это ради одного сообщения.
Теперь для этого есть отдельный метод. Один HTTP-запрос отправляет одно сообщение конкретному человеку, со своим текстом и вложением. Всё остальное про API рассылок мы разбирали в отдельной статье, здесь только про точечную отправку.
Как это выглядит
Схема простая: событие в вашей системе → запрос к API → сообщение у клиента.
Весь метод — один запрос POST /v1/message, и в нём три вещи: с какого аккаунта отправить, кому и что. Ключ тот же, что для рассылок, из раздела API личного кабинета:
curl -X POST https://api.wsender.ru/v1/message \
-H "Authorization: key ВАШ_ТОКЕН" \
-H "Content-Type: application/json" \
-d '{
"channelId": "ИД_АККАУНТА",
"phone": "+7 999 123-45-67",
"message": { "kind": "text", "text": "Ваш заказ 84421 оплачен, ждём вас завтра в 12:00." },
"idempotencyKey": "order-84421-paid"
}'
В ответ приходит сообщение со своим идентификатором и статусом:
{
"id": "0f3c9a6e-4b12-4d8a-9f31-6c5a71e08d42",
"status": "Queued",
"channelId": "ИД_АККАУНТА",
"target": "Phone",
"phone": "79991234567",
"idempotencyKey": "order-84421-paid"
}
Идентификатор аккаунта берётся из списка подключённых, GET /v1/channel, а idempotencyKey — ваш собственный ключ заявки, о нём ниже.
Ответ на запрос приходит сразу, но означает он «принято в очередь», а не «доставлено». Сообщение отправляет ваш подключённый аккаунт, живой, из приложения мессенджера. На это нужны секунды, а по незнакомому номеру до пары минут: мессенджеры не любят, когда номера проверяют мгновенно. С бота быстрее: он отправляет напрямую через Bot API, без живого приложения.
Разница честная и важная для оператора на линии. Правильная фраза в трубку: «отправляю вам сообщение». Не «уже пришло».
Кому отправлять в Ватсапе, Телеграме, Максе и ботах
Адресат задаётся одним из способов, и это решает, нужен ли номер вообще:
- Номер телефона в любом привычном виде. Самый частый случай: номер у вас уже есть в карточке клиента.
- Имя аккаунта в Телеграме и Максе, если оно известно вашему аккаунту по прошлой переписке.
- Диалог по идентификатору из API или по адресу диалога в самом мессенджере. Диалог при этом должен быть уже известен вашему аккаунту: незнакомый адрес запрос отклонит, а не попробует «наугад».
В запросе это четыре поля, из которых заполняется ровно одно — сервис не угадывает, кому вы хотели написать:
"phone": "+7 999 123-45-67"
"username": "@ivanov"
"dialogId": "ИД_ДИАЛОГА_ИЗ_API"
"dialogMid": "123456789"
Последний вариант — адрес диалога в самом мессенджере, chat_id у ботов Телеграма и Макса. Он удобен, когда ваша система уже хранит адреса мессенджера и заводить у себя наши идентификаторы не хочет.
Для ботов работает только адресация диалогом. Причина не в сервисе: у подписчика бота может не быть ни номера, ни имени аккаунта, зато диалог есть всегда. Писать первым бот всё равно не может, и это ограничение платформы, а не наше.
Что можно отправить
Текст, картинку, видео, аудио, документ или карточку контакта. Текст поддерживает варианты формулировок в квадратных скобках: [Здравствуйте|Добрый день] превратится в одно из двух, чтобы сообщения не выглядели штампованными.
Здесь есть ловушка, о которой лучше узнать до первого запроса, а не после: квадратные скобки считаются спинтаксом даже без вариантов. Текст [Важно] ваш код 1234 придёт клиенту как Важно ваш код 1234, без скобок. Если ваши шаблоны используют скобки как оформление, замените их на другой символ.
Файл загружается один раз отдельным запросом, а дальше передаётся идентификатором в любом числе сообщений. Один прайс-лист, отправленный сотне клиентов, занимает место одного файла. Ограничения простые: до 10 МБ на файл, а неиспользованный файл хранится сутки и удаляется сам.
Загрузка — обычная форма с файлом, в ответе главное поле id:
curl -X POST https://api.wsender.ru/v1/file \
-H "Authorization: key ВАШ_ТОКЕН" \
-F "file=@price-2026.pdf"
Если файл уже лежит по публичной ссылке, вместо формы есть POST /v1/file/url с полем url — сервер скачает его сам. Дальше идентификатор подставляется в сообщение, а тип задаётся полем kind: text, image, video, audio, file или contact. У картинки, видео и документа есть подпись caption, у любого вложения — имя fileName, которое увидит получатель:
{
"channelId": "ИД_АККАУНТА",
"phone": "+7 999 123-45-67",
"message": {
"kind": "file",
"fileId": "ИД_ЗАГРУЖЕННОГО_ФАЙЛА",
"fileName": "Прайс-лист на осень",
"caption": "Добрый день! Прайс, о котором говорили. Цены действуют до конца месяца."
},
"idempotencyKey": "price-request-1187"
}
Имя необязательное: без него возьмётся имя загруженного файла, а расширение подставится в любом случае — его вы не передаёте. Картинка отправляется так же, с "kind": "image", а карточка контакта обходится без файла: { "kind": "contact", "phone": "+7 999 000-00-00" }.
Единственное исключение: карточку контакта нельзя отправить с бота, у Bot API такого типа сообщения нет. Запрос отклоняется сразу, а не теряется молча в отправке.
Как узнать, что сообщение дошло
Ответ на запрос сказал «принято». Дальше сервис сам сообщит, чем всё кончилось, тремя событиями вебхука:
message.sent. Сообщение ушло в мессенджер.message.failed. Отправить не удалось, и в событии есть причина: получатель заблокировал аккаунт, писать ему первым нельзя, аккаунт отправки отключился. Или отправка несколько минут упиралась во временный сбой мессенджера, и попытки кончились: такое сообщение можно просто поставить заново.message.notfound. Адресата в мессенджере нет: ни такого номера, ни такого аккаунта. Это отдельное событие, а не разновидность ошибки — чинить нечего, а вот пометить контакт в своей базе стоит.
Подписка на них явная. Укажите адрес вебхука и перечислите эти события в его фильтре: без этого они не придут, даже если вебхук уже настроен на события рассылок. Так сделано намеренно. У кого сотня сообщений в день, тому поток по каждому сообщению полезен, а у кого рассылки на десятки тысяч получателей, тот не должен получать его случайно.
Что приходит в событии: идентификатор сообщения, аккаунт отправки, адресат в том виде, в каком вы его задали, статус, причина неудачи и даты. И главное: ваш собственный ключ, тот самый, который вы передали с запросом. По нему вы находите свою заявку в своей системе и не храните наши идентификаторы вообще.
Так выглядит событие о неудаче — получатель заблокировал ваш аккаунт:
{
"id": "7d1e0b58-3a44-4f2c-9c81-15b0f7a9d6e3",
"type": "message.failed",
"date": "2026-09-07T09:15:42Z",
"payload": {
"message": {
"id": "0f3c9a6e-4b12-4d8a-9f31-6c5a71e08d42",
"channelId": "ИД_АККАУНТА",
"phone": "79991234567",
"status": "Failed",
"errorReason": "Blocked",
"failDate": "2026-09-07T09:15:41Z",
"idempotencyKey": "order-84421-paid"
}
}
}
Причина подсказывает действие: Blocked и RestrictedSend — этому человеку с этого аккаунта писать нельзя; ChannelDisconnected — аккаунт отключился, подключите его и отправьте заново; Expired и Unavailable — отправить не удалось за отведённый срок, сообщение можно поставить снова с новым ключом.
Три обещания, на которые можно рассчитывать при сборке обработчика:
- Событие не продублируется. Отправляющий агент может доложить об одной отправке дважды (потерялся ответ, ушёл повтор), но событие уйдёт вам один раз.
- Ваша собственная отмена события не даёт. Вы отменили сообщение сами и получили ответ на этот запрос: сообщать вам о вашем же действии второй раз незачем.
- Молчания не будет. Временные сбои сервис переживает сам: мессенджер попросил подождать или отвалилась сеть, и сообщение уйдёт повторно через несколько секунд, не нарушая порядок остальных. А если отправить так и не удалось за отведённый срок, оно не зависнет в статусе «в очереди» навсегда: придёт
message.failedс причиной. Ждать вечно и гадать не придётся.
Если вебхуки делать не хочется, статус сообщения можно запросить по его идентификатору: GET /v1/message/{id} вернёт ту же структуру, что и ответ на отправку, с текущим status — Queued, Sending, Sent, Failed или NotFound. Но честно: сразу после запроса статус будет «в очереди», и узнать исход одним обращением не выйдет — придётся спрашивать повторно. Режима «жди, пока отправится» у метода нет намеренно: держать ваш HTTP-запрос открытым на минуту хуже, чем прислать событие. Поэтому вебхуки здесь предпочтительный путь, а не украшение.
Ответ на входящее — тем же методом
Метод не только для исходящих. О новом сообщении от клиента сервис сообщает событием message.received: в нём текст, отправитель и диалог. Дальше вы отвечаете тем же запросом отправки, указав адресатом диалог из события. Так на одном методе собирается двусторонний обмен: свой инбокс, чат-бот или связка с CRM.
Ответ можно поставить цитатой на конкретное сообщение — как в самом мессенджере, когда отвечаешь на реплику из середины разговора. Для этого в запросе передаётся идентификатор цитируемого сообщения: он есть и в событии о входящем, и в истории диалога, которую API отдаёт целиком.
Тот же POST /v1/message, только адресат — диалог, и добавлено поле replyToMid:
{
"channelId": "ИД_АККАУНТА",
"dialogId": "ИД_ДИАЛОГА_ИЗ_СОБЫТИЯ",
"replyToMid": "MID_СООБЩЕНИЯ_КЛИЕНТА",
"message": { "kind": "text", "text": "Да, есть в наличии. Забронировать за вами до вечера?" },
"idempotencyKey": "reply-5c2e-1"
}
Оба идентификатора приходят в событии о входящем: диалог в payload.dialog.id, само сообщение — в payload.message.mid. Цитировать можно только сообщение того же диалога. Чужое сервис не примет: проверка идёт в момент запроса, а не молча теряется при отправке.
Одно исключение — бот Яндекса: цитат там нет совсем. Поле ответа у платформы принимает только число, а идентификаторы сообщений в ней строковые, сослаться нечем. Запрос с цитатой сервис отклонит сразу и скажет причину — это честнее, чем сообщение, которое ушло бы к клиенту без цитаты незаметно для вас. В остальных шести каналах цитируется любой тип сообщения.
Оригинал могут удалить уже после вашего запроса. Аккаунт мессенджера тогда отправит сообщение обычным, без цитаты: терять текст из-за оформления незачем. Боты так не умеют — сообщение завершится ошибкой с причиной, по которой понятно, что делать: отправить заново без цитаты. У ботов Макса и ВКонтакте сервис проверяет цитируемое сообщение заранее, ещё до отправки.
Повтор запроса не создаёт второе сообщение
Отдельная беда транзакционных сообщений: сеть отвалилась в момент ответа, ваш код повторил запрос, клиент получил два одинаковых сообщения.
Передайте с запросом свой уникальный ключ, и повтор с тем же ключом вернёт то же самое сообщение с его текущим статусом, а не отправит второе. Это стандартная механика идемпотентности, и здесь она работает на уровне самого сообщения.
Пока сообщение ждёт в очереди, его можно отменить отдельным запросом — DELETE /v1/message/{id}. Как только его взял в работу отправляющий аккаунт, отменять поздно: придёт код 409 и текущий статус в теле, а исход решит сама отправка. Отменённое сообщение дальше читается как Failed с причиной Canceled, отдельного статуса «отменено» нет.
Чего сервис не даст сделать
Честно про границы, чтобы вы не проектировали лишнего.
- Проверка запрещённых тем работает и здесь. Сообщение проверяется в момент запроса, отклонённое в очередь не попадает, и в ответе будет причина. Прямая отправка не обходной путь мимо модерации.
- Дневной лимит тарифа общий с рассылками. Прямые сообщения расходуют его наравне: одно отправленное сообщение это одно сообщение тарифа, своего лимита у метода нет. Доплаты за превышение здесь не предусмотрено: исчерпанный лимит это отказ запроса, а не платная отправка. И остаток уменьшается на то, что уже стоит в очереди проекта.
- Между отправками с одного аккаунта выдерживается пауза в несколько секунд. Это страховка аккаунта от потока «из цикла»: живой разговор с клиентом в неё не упирается, а попытка разослать тысячу сообщений через этот метод упрётся.
- Своих номеров сервис не выдаёт. Сообщение уходит с вашего подключённого аккаунта или бота: репутация номера остаётся вашей, а ответы клиентов приходят вам в обычное приложение мессенджера.
Что дальше
Метод работает на обычных тарифах и сейчас проходит тестирование: своей цены у него пока нет, сообщения считаются как обычные сообщения тарифа. На бесплатном тарифе 15 сообщений в день, для сборки интеграции этого хватает.
Начните с одного запроса на свой номер: подключите аккаунт, получите ключ в разделе API личного кабинета и отправьте себе тестовое сообщение. Дальше добавьте подписку на события и повесьте вызов на то место в своей системе, где рождается событие.
Полное описание методов и возможность выполнить запрос прямо из браузера есть в документации API, спецификация для генерации клиента в OpenAPI-файле.
* WhatsApp принадлежит компании Meta, деятельность которой признана в России экстремистской и запрещена.