API ЭТОЧАТБОТ

Контакты: как адресовать человека

Что такое chat_id, где его взять, как привязать к нему свой external_id — и почему chat_id приходит строкой, а не числом.

Чтобы что-то отправить человеку через API — сообщение или запуск сценария — нужно объяснить нам, кому именно. Для этого есть два способа адресации: наш chat_id и ваш собственный external_id, привязанный к контакту. Разберём оба.

chat_id — наш адрес человека

chat_id — это идентификатор конкретного человека в конкретном боте. Он появляется в тот момент, когда человек впервые написал вашему боту: нажал «Старт» в Telegram, отправил сообщение в чате на сайте, написал в сообщения сообщества ВКонтакте.

Один и тот же человек в двух ваших ботах — это два разных контакта с разными chat_id. Ключ API привязан к одному боту, поэтому и chat_id вы всегда используете в пределах этого бота: чужой идентификатор просто не найдётся.

Главное ограничение, и оно не наше. Написать человеку можно только если он раньше сам обратился к боту. До первого обращения адреса доставки не существует ни в одном канале — так устроены сами мессенджеры. Telegram разрешает писать только тем, кто хотя бы раз запускал бота; ВКонтакте требует от человека разрешения на сообщения от сообщества. Поэтому сценарий «выгрузим базу клиентов из CRM и разошлём всем напоминания» не заработает: рассылать можно только тем, кто уже писал боту.

Если попробовать обратиться к человеку, которого бот не знает, вы получите честный отказ:

{
  "error": {
    "code": "contact_not_found",
    "message": "Контакт не найден. Человек должен сначала написать боту — до этого адреса доставки не существует."
  }
}

Два способа узнать chat_id

Способ 1: запросить список контактов

GET /{channel}/chats отдаёт всех, кто писал боту. Подходит, чтобы разово синхронизировать базу или найти человека по имени. Подробности — ниже в этой статье.

Способ 2: зеркалирование входящих

В кабинете на странице «Для разработчиков» можно указать до трёх адресов, куда мы будем зеркалить входящие сообщения. Как только человек написал боту, на ваш адрес прилетает событие — и в нём есть chat_id.

Это удобнее для живой интеграции: вы узнаёте адрес в момент первого контакта и сразу сохраняете его у себя рядом с профилем пользователя, вместо того чтобы периодически перечитывать весь список контактов.

Важно: chat_id — это строка

В ответах API chat_id всегда приходит строкой, и в теле запросов его тоже лучше отправлять строкой. Это не каприз формата.

У чата на сайте идентификаторы длиннее 16 цифр, а числа в JavaScript — это 64-битные float. Всё, что длиннее ~16 значащих цифр, молча округляется. Проверьте сами:

// пришло от API строкой
const chatId = "1753086420123456789";

Number(chatId);          // 1753086420123456800  ← последние цифры потеряны
String(Number(chatId));  // "1753086420123456800"

Самое неприятное — что ошибка не выбрасывается. Вы получаете «почти правильный» идентификатор, отправляете его нам и ловите contact_not_found на человеке, который совершенно точно писал боту.

Та же ловушка срабатывает при парсинге JSON, если идентификатор где-то по пути превратился в число:

JSON.parse('{"chat_id":1753086420123456789}');
// { chat_id: 1753086420123456800 }  ← сломалось ещё до вашего кода

Практические выводы:

  • Храните chat_id в текстовом поле базы (VARCHAR), а не в BIGINT, если ваш язык не гарантирует 64-битную целочисленную арифметику.
  • Не прогоняйте его через parseInt, Number, +chatId и подобное.
  • В электронных таблицах при экспорте тоже задавайте текстовый формат — иначе они округлят молча, как и JS.

GET /{channel}/chats — список людей

Возвращает контакты бота, отсортированные по свежести: последние писавшие — первыми.

Параметры запроса

ПараметрПо умолчаниюОписание
limit50Сколько контактов вернуть. Максимум — 100.
offset0Сколько пропустить с начала. Для постраничного обхода.
queryПоиск по имени, фамилии и username. Ищет подстроку.

Запрос

curl -s "https://etochat.bot/api/v2/{channel}/chats?limit=2" \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"

Ответ

{
  "total": 123,
  "limit": 2,
  "offset": 0,
  "contacts": [
    {
      "chat_id": "9995735656",
      "name": "Иван",
      "username": "ivan",
      "status": "open",
      "unread": 0,
      "blocked_bot": false,
      "external_ids": ["user-42"],
      "created_at": "2026-07-18 11:04:12",
      "updated_at": "2026-07-21 09:31:50"
    },
    {
      "chat_id": "1753086420123456789",
      "name": "Гость с сайта",
      "username": "",
      "status": "open",
      "unread": 2,
      "blocked_bot": false,
      "external_ids": [],
      "created_at": "2026-07-21 08:12:03",
      "updated_at": "2026-07-21 08:20:44"
    }
  ]
}

Поля контакта

ПолеЧто это
chat_idНаш адрес человека. Строка.
nameИмя и фамилия, как их отдал мессенджер.
usernameUsername, если канал его сообщает. Часто пустой.
statusСостояние диалога в инбоксе, например open.
unreadСколько непрочитанных сообщений от человека.
blocked_bottrue, если человек остановил бота. Отправка ему вернёт 409.
external_idsВаши идентификаторы, привязанные к этому контакту. Массив.
created_at, updated_atКогда контакт появился и когда была последняя активность.

Постраничный обход

total — общее число контактов по текущему фильтру, поэтому обойти базу можно простым циклом:

curl -s "https://etochat.bot/api/v2/{channel}/chats?limit=100&offset=0"   -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"
curl -s "https://etochat.bot/api/v2/{channel}/chats?limit=100&offset=100" -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"

Список отсортирован по времени последней активности, а активность идёт постоянно — за время долгого обхода контакты могут переехать между страницами. Если вам важна полнота, ориентируйтесь не на «страницу», а на chat_id: складывайте результат в словарь по нему, тогда сдвиг ничего не испортит.

Поиск

curl -s "https://etochat.bot/api/v2/{channel}/chats?query=Иван" \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
  --get --data-urlencode "query=Иван"

Поиск идёт по имени, фамилии и username, по подстроке. По chat_id и по external_id он не ищет — для них есть отдельные эндпоинты.

GET /{channel}/chats/{chat_id} — карточка одного человека

Когда chat_id уже известен и нужно свежее состояние: не остановил ли человек бота, есть ли непрочитанные, какие внешние id к нему привязаны.

curl -s https://etochat.bot/api/v2/{channel}/chats/9995735656 \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ"
{
  "contact": {
    "chat_id": "9995735656",
    "name": "Иван",
    "username": "ivan",
    "status": "open",
    "unread": 0,
    "blocked_bot": false,
    "external_ids": ["user-42"],
    "created_at": "2026-07-18 11:04:12",
    "updated_at": "2026-07-21 09:31:50"
  }
}

Если контакта нет у этого бота — 404 contact_not_found. Если chat_id не похож на число — 400 chat_id_invalid.

Полезный приём перед отправкой: проверьте blocked_bot. Если там true, человек остановил бота, и попытка отправки вернёт 409 recipient_blocked_bot. Лучше сразу пометить это у себя и, например, отправить письмо вместо сообщения в мессенджер.


external_id — ваш собственный идентификатор

В вашей системе человек известен под своим id: user-42, CRM-8891, UUID из базы. Хранить рядом ещё и наш chat_id, следить за его актуальностью, тащить в каждый вызов — лишняя работа.

Поэтому есть привязка: вы один раз сообщаете нам «этот chat_id — это мой пользователь user-42», и дальше адресуете человека своим идентификатором. Наш chat_id можно у себя вообще не хранить.

POST /{channel}/contacts/link — привязать

Запрос

curl -s -X POST https://etochat.bot/api/v2/{channel}/contacts/link \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"chat_id":"9995735656","external_id":"user-42"}'

Ответ

{
  "linked": true,
  "external_id": "user-42",
  "chat_id": "9995735656"
}

Что важно знать про external_id:

  • До 190 символов. Длиннее — 400 external_id_too_long.
  • Регистр сохраняется: User-42 и user-42 — разные идентификаторы.
  • Он живёт в пределах одного бота, как и chat_id.
  • Повторный вызов с тем же external_id перепривязывает его на новый chat_id. Это нормальный способ переезда: если человек начал писать боту с другого аккаунта, просто вызовите link ещё раз с новым chat_id.
  • У одного контакта может оказаться несколько ваших идентификаторов — все они видны в поле external_ids.

Если chat_id в теле запроса не найден у бота — 404 contact_not_found. Если external_id пустой — 400 external_id_required.

Куда подставлять external_id дальше

Везде, где принимается получатель, вместо chat_id можно передать external_id. Отправка сообщения:

curl -s -X POST https://etochat.bot/api/v2/{channel}/messages \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"user-42","text":"Ваш тариф продлён до 14 августа."}'

Запуск сценария:

curl -s -X POST https://etochat.bot/api/v2/{channel}/trigger \
  -H "Authorization: Bearer etchtbt_bot_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{"external_id":"user-42","vars":{"plan":"Премиум","days_left":"3"}}'

Указывать одновременно и chat_id, и external_id не нужно: если передан chat_id, используется он.

Если внешний идентификатор ещё не привязан, вы получите:

{
  "error": {
    "code": "external_id_not_linked",
    "message": "external_id «user-42» не привязан. Сначала вызовите POST /{channel}/contacts/link."
  }
}

Типичный порядок работы

  1. Человек пишет боту. Вы узнаёте его chat_id — из зеркалирования входящих или из GET /{channel}/chats.
  2. Понимаете, кто это в вашей системе. Например, человек оставил в диалоге почту, или пришёл по ссылке с меткой.
  3. Вызываете POST /{channel}/contacts/link и связываете chat_id с вашим user-42.
  4. Дальше во всех вызовах API используете только external_id.

Шаг 3 достаточно сделать один раз на человека. Повторная привязка того же значения ничего не ломает — она идемпотентна по смыслу и просто перезапишет соответствие.


Ошибки, связанные с адресацией

СтатусКодКогда
400chat_id_invalidchat_id не число: пустой, с пробелами, обрезанный.
400recipient_requiredВ теле нет ни chat_id, ни external_id.
400external_id_requiredВ POST /{channel}/contacts/link не передан external_id.
400external_id_too_longexternal_id длиннее 190 символов.
404contact_not_foundТакого контакта у этого бота нет — человек не писал боту.
404external_id_not_linkedВнешний id не привязан, нужен POST /{channel}/contacts/link.
409recipient_blocked_botЧеловек остановил бота, доставка невозможна.

Все ошибки приходят одинаково: JSON вида {"error":{"code":"…","message":"…"}} с осмысленным HTTP-статусом.

Что дальше

Когда адресация настроена, остаётся собственно действие: отправить сообщение через POST /{channel}/messages или — что чаще правильнее — запустить сценарий через POST /{channel}/trigger, оставив тексты и логику в конструкторе, а из своей системы передавая только переменные.