Любой запрос к API бота подписывается ключом. Ключ говорит серверу две вещи: кто пришёл и с каким ботом ему разрешено работать. Ничего другого для авторизации не нужно — ни логина, ни отдельного токена, ни OAuth.
Как выглядит ключ
Ключ начинается с префикса etchtbt_bot_, дальше идут 48 шестнадцатеричных символов:
etchtbt_bot_3f9a1c04d7b28e5610af43c9d0e7215b8c4f6a39d2e17b05
Префикс удобен тем, что ключ легко узнать глазами и легко отловить автоматическими сканерами секретов в репозитории.
Как выпустить ключ
- Войдите в кабинет и выберите бота, для которого нужен доступ.
- Откройте раздел «Для разработчиков».
- Перейдите на вкладку «Ключи API».
- Нажмите «Выпустить ключ».
- Скопируйте показанное значение и сразу положите его туда, где оно будет жить постоянно: переменную окружения на сервере, менеджер секретов, настройки CI. Не в чат, не в заметки, не в письмо.
Сразу после этого проверьте ключ запросом GET /{channel}/me — это самый дешёвый способ убедиться, что всё скопировалось без потерянного символа:
curl -s https://etochat.bot/api/v2/{channel}/me \
-H "Authorization: Bearer $ETOCHAT_API_KEY"
Ответ:
{
"bot": {"id": 83, "name": "Бот поддержки", "channel": "webchat", "username": ""},
"can_send": true
}
Поле can_send: false означает, что подписка на боте неактивна: читать данные вы сможете, а отправка сообщений будет отклонена с ошибкой 402 subscription_inactive. Сам ключ при этом исправен.
Почему ключ показывается только один раз
В нашей базе хранится не ключ, а его хэш (sha256). Когда приходит запрос, мы считаем хэш присланного значения и сравниваем с сохранённым. Обратного пути нет: из хэша исходную строку не восстановить.
Это значит, что никто не может показать вам ключ повторно — ни вы в кабинете, ни поддержка сервиса. И это не неудобство, а свойство, ради которого так сделано: даже если кто-то получит доступ к нашей базе, готовых ключей он там не найдёт.
Практический вывод: момент выпуска — единственная возможность сохранить ключ. Не закрывайте окно, пока не убедились, что значение записано в надёжное место.
Потеряли ключ — что делать
Восстановить нельзя, но заменить можно за минуту:
- Выпустите новый ключ на той же странице.
- Пропишите его на сервере, перезапустите приложение.
- Убедитесь, что интеграция работает на новом ключе (
GET /{channel}/meвернул 200). - Отзовите старый ключ.
Порядок именно такой. Если сначала отозвать, а потом выпускать, интеграция полежит в промежутке: все запросы со старым ключом будут отваливаться с 401 invalid_key.
Тот же порядок годится для плановой ротации ключей — она не требует простоя как раз потому, что на боте одновременно могут жить несколько ключей.
Лимит: 5 живых ключей на бота
На одного бота можно держать до пяти неотозванных ключей. Этого достаточно, чтобы:
- разделить окружения (боевой сервер, тестовый стенд, локальная разработка);
- дать отдельный ключ подрядчику и отозвать именно его, когда работа закончится;
- спокойно проводить ротацию: новый ключ уже работает, старый ещё не отозван.
Если лимит исчерпан, кнопка выпуска перестанет работать до тех пор, пока вы не отзовёте что-нибудь ненужное. Ключи, которыми никто не пользуется, лучше отзывать сразу — это уменьшает площадь для утечки.
Ключ привязан к одному боту
Ключ открывает доступ только к тому боту, на котором он выпущен. Даже если у вас в кабинете десять ботов и все они ваши, ключ от бота №83 не видит контакты бота №84 и не может отправить туда сообщение. Нужен доступ к нескольким ботам — выпустите ключ на каждом и храните их по отдельности.
Отсюда же следует полезное правило: ключ можно отдать подрядчику, который делает интеграцию для одного конкретного бота, не выдавая ему доступ ко всему кабинету.
Отдельно про каналы: API работает с чатом на сайте, Telegram, ВКонтакте и МАКС. Instagram не поддерживается из-за правил Meta на исходящие сообщения — ключ от Instagram-бота выпустится, но любой запрос вернёт 400 unsupported_channel. Для Instagram у нас есть отдельное партнёрское API.
Как передавать ключ в запросе
Ключ передаётся заголовком Authorization по схеме Bearer. Никаких параметров в URL, никаких полей в теле — только заголовок.
Authorization: Bearer etchtbt_bot_3f9a1c04d7b28e5610af43c9d0e7215b8c4f6a39d2e17b05
curl
Ключ берём из переменной окружения, чтобы он не осел в истории командной строки:
export ETOCHAT_API_KEY='etchtbt_bot_3f9a1c04d7b28e5610af43c9d0e7215b8c4f6a39d2e17b05'
# проверка ключа
curl -s https://etochat.bot/api/v2/{channel}/me \
-H "Authorization: Bearer $ETOCHAT_API_KEY"
# отправка сообщения
curl -s https://etochat.bot/api/v2/{channel}/messages \
-H "Authorization: Bearer $ETOCHAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"chat_id":"9995735656","text":"Заказ собран, курьер выехал"}'
PHP
<?php
function etochat(string $method, string $path, ?array $payload = null): array
{
$key = getenv('ETOCHAT_API_KEY');
if (!$key) {
throw new RuntimeException('ETOCHAT_API_KEY не задан');
}
$headers = [
'Authorization: Bearer ' . $key,
'Accept: application/json',
];
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 15,
];
if ($payload !== null) {
$headers[] = 'Content-Type: application/json';
$options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_UNESCAPED_UNICODE);
}
$options[CURLOPT_HTTPHEADER] = $headers;
$ch = curl_init('https://etochat.bot/api/v2/{channel}' . $path);
curl_setopt_array($ch, $options);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode((string) $body, true) ?: [];
if ($status >= 400) {
$code = $data['error']['code'] ?? 'http_' . $status;
$msg = $data['error']['message'] ?? 'Неизвестная ошибка';
throw new RuntimeException("etochat {$code}: {$msg}");
}
return $data;
}
// проверка ключа
$me = etochat('GET', '/me');
echo $me['bot']['name'] . ' (' . $me['bot']['channel'] . ')' . PHP_EOL;
// отправка сообщения
etochat('POST', '/messages', [
'chat_id' => '9995735656',
'text' => 'Заказ собран, курьер выехал',
]);
Обратите внимание: chat_id передаётся строкой. У чата на сайте он длиннее 16 цифр, и в JavaScript такое число теряет точность — поэтому мы и отдаём, и принимаем его как строку.
Python
import os
import requests
BASE = "https://etochat.bot/api/v2/{channel}"
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {os.environ['ETOCHAT_API_KEY']}",
"Accept": "application/json",
})
def call(method: str, path: str, payload: dict | None = None) -> dict:
r = session.request(method, BASE + path, json=payload, timeout=15)
data = r.json()
if r.status_code >= 400:
err = data.get("error", {})
raise RuntimeError(f"etochat {err.get('code')}: {err.get('message')}")
return data
# проверка ключа
me = call("GET", "/me")
print(me["bot"]["name"], me["bot"]["channel"], me["can_send"])
# отправка сообщения
call("POST", "/messages", {
"chat_id": "9995735656",
"text": "Заказ собран, курьер выехал",
})
Session здесь не для красоты: заголовок с ключом задаётся один раз и дальше уезжает со всеми запросами, так что его негде забыть.
Безопасность
Относитесь к ключу как к паролю от почты компании, а не как к идентификатору приложения.
Что даёт ключ тому, у кого он есть. Полный доступ к переписке бота: выгрузить все контакты со всеми именами, прочитать карточки, отправить любому человеку сообщение от вашего имени, запустить сценарий автоматизации. Утёкший ключ — это не «кто-то узнал номер нашего бота», это «кто-то может писать вашим клиентам».
Где ключ должен жить. Только на сервере: переменные окружения, файл конфигурации вне веб-корня, менеджер секретов, зашифрованные секреты CI. Запросы к API делает ваш бэкенд.
Где ключа быть не должно:
- в JavaScript на странице, в React/Vue-сборке, в мобильном приложении — всё это отдаётся пользователю, и «спрятать» там ничего нельзя;
- в публичном репозитории и вообще в коммитах: даже удалённый следующим коммитом ключ остаётся в истории git;
- в тикетах, чатах, письмах и скриншотах;
- в логах — если логируете HTTP-запросы, вычищайте заголовок
Authorization.
Разделяйте доступы. Тестовому стенду — свой ключ, подрядчику — свой. Тогда отзыв одного ключа не роняет всё остальное.
При малейшем подозрении на утечку — отзывайте немедленно. Отзыв мгновенный: со следующего запроса ключ отдаёт 401 invalid_key. Сначала выпустите замену и раскатите её, потом отзовите скомпрометированный. Если утечка серьёзная и счёт идёт на минуты — отзывайте сразу, а интеграцию поднимете на новом ключе следом.
Ключ можно только отозвать — «приостановить» или «сменить пароль» у него нельзя. Отозванный ключ не восстанавливается.
Чем ключ API отличается от ключа виджета чата на сайте
Их легко перепутать, потому что оба называются «ключом» и оба относятся к одному боту. Но это принципиально разные вещи.
| Ключ API | Ключ виджета чата на сайте | |
|---|---|---|
| Где находится | на вашем сервере | прямо в HTML-коде страницы |
| Кто его видит | только вы | любой посетитель сайта |
| Что им можно | читать контакты, писать людям, запускать сценарии | открыть окно виджета и принять сообщение от посетителя |
| Можно ли отправлять сообщения | да | нет |
| Как передаётся | заголовок Authorization: Bearer | в коде подключения виджета |
Ключ виджета публичный по замыслу: он обязан лежать в открытом виде на странице, иначе виджет не сможет загрузиться у посетителя. Поэтому за ним не стоит никаких прав — он только помогает понять, чей это виджет, и принять входящее сообщение. Отправить им что-либо от имени бота нельзя.
Ключ API — ровно наоборот: он секретный и может почти всё. Если вы вставите ключ API в код страницы, вы фактически опубликуете доступ к переписке бота.
Практическое правило: если значение видно в исходном коде страницы — это не ключ API. Если это ключ API — верните его на сервер и немедленно отзовите тот, что успел засветиться.
Ошибки, связанные с ключом
Ошибки всегда приходят в одинаковом виде:
{"error": {"code": "invalid_key", "message": "Ключ неверен или отозван"}}
| Статус | Код | Что случилось |
|---|---|---|
| 401 | unauthorized | заголовок Authorization вообще не передан |
| 401 | invalid_key | ключ неверный, отозван или бот отключён |
| 400 | unsupported_channel | канал бота не поддерживается (Instagram) |
| 402 | subscription_inactive | подписка неактивна, отправка невозможна |
Если получаете unauthorized, хотя ключ вроде бы передаёте, проверьте типовые причины: потерянное слово Bearer, лишний пробел или перевод строки в конце значения, прокси или CDN, вырезающий заголовок Authorization.
Если получаете invalid_key — сверьте, тот ли ключ подставился (окружения легко перепутать), не отозван ли он в кабинете и включён ли сам бот.
Дальше: как найти chat_id и отправить первое сообщение. И держите в голове главное ограничение API: написать человеку можно только после того, как он сам обратился к боту — до первого обращения адреса доставки не существует ни в одном канале.