Сообщение - API (Отправка запроса)

API-сообщение: вызов вашего сервера из сценария бота

API-сообщение — это шаг сценария, который вместо текста в Telegram отправляет HTTP-запрос на ваш сервер,
а ответ сервера сохраняет в переменную и передаёт в следующие сообщения цепочки. Так бот может
показать пользователю баланс из вашей CRM, номер заказа из вашей базы, результат проверки промокода и т.п.

Документ для разработчика принимающей стороны: что приходит, как ответить, как вывести ответ в боте.


1. Как это работает

пользователь → ... → [API-сообщение] → HTTP-запрос на ваш сервер
                                            │
                                            ▼ HTTP-код ответа
                              ┌─────────────┼──────────────┐
                            2xx           4xx            5xx / нет ответа
                              │             │              │
                       «успех»      «ошибка клиента»  «ошибка сервера»
                              │             │              │
                              ▼             ▼              ▼
                   следующее сообщение цепочки: в тексте доступны {RESULT}, {RESULT.поле}, {RESULT_STATUS}
ЧтоКак
Когда срабатываетКогда пользователь доходит до этого шага (нажал кнопку, оплатил, пришёл по переходу)
Куда идёт запросНа URL из настроек, методом GET / POST / PUT / PATCH / DELETE
Что определяет дальнейший путьHTTP-код ответа: 2xx → «успех», 4xx → «ошибка клиента», всё остальное (5xx, таймаут, нет соединения) → «ошибка сервера»
ПовторыПри сетевой ошибке или 5xx запрос повторяется столько раз, сколько указано в настройке «Повторов при ошибке» (0–5, по умолчанию 1). Ветка ошибки вызывается только если все попытки неудачны. 4xx не повторяется
Если ветка не заданаЦепочка завершается. Общего «следующего сообщения» у API-сообщения нет — только три ветки
Что получает следующее сообщениеТело ответа, HTTP-код и ID запроса — как плейсхолдеры (раздел 5)

2. Настройка в личном кабинете

Сообщение → тип API → вкладки:

ВкладкаЧто задаётся
НастройкиURL-адрес (только https://), формат (Content-Type), тип запроса, прокси (опционально), повторов при ошибке
ПараметрыТри списка «ключ → значение»: Header, Query, Body. В ключах и значениях работают плейсхолдеры (раздел 3). Ключ body с точкой даёт вложенный объект: user.name → {"user": {"name": "..."}}
СообщенияТри ветки: «Сообщение об успехе», «об ошибке клиента», «об ошибке сервера»
ТестВыполняет запрос от вашего имени, показывает отправленное и ответ, даёт скопировать {RESULT.поле}
ЛогиВсе запросы: статус, попытки, длительность, ID запроса, отправленные данные и ответ

Body отправляется как JSON (Content-Type: application/json, если не переопределён параметром Header).


3. Что получает ваш сервер

Системные заголовки

Добавляются всегда, если вы не задали заголовок с таким же именем в параметрах:

ЗаголовокЗначениеЗачем
X-Bott-Request-IdUUID, например 3f1c9b2e-7d1a-4a3b-9c1e-2b6f4d8a1c55Уникальный ID запроса. При повторах не меняется — по нему дедуплицируйте. Он же виден в логах ЛК и доступен в боте как {RESULT_REQUEST_ID}
X-Bott-Bot-IdID ботаЕсли один сервер обслуживает несколько ботов
X-Bott-Message-IdID API-сообщенияКакой шаг сценария вызвал запрос
X-Bott-User-IdID пользователя в BottВнутренний ID (не Telegram ID). Telegram ID передайте параметром {USER_TELEGRAM_ID}
User-AgentBott-MessageApi/1.0
Content-Typeиз настройки «Формат»

Плейсхолдеры для подстановки в URL, заголовки, query и body

ПлейсхолдерЗначение
{USER_TELEGRAM_ID}Telegram ID пользователя
{USER_ID}ID пользователя в боте
{USERNAME}@username
{FIRST_NAME}, {SECOND_NAME}, {NAME}Имя, фамилия, имя-ссылка
{BALANCE}, {BALANCE_WITHOUT_CURRENCY}Баланс пользователя в боте
{ATTR_ключ}Значение атрибута пользователя (например {ATTR_city})
{USER_ATTRIBUTES}Все атрибуты списком
{ORDER_ID}, {ORDER_KEY}Заказ, если шаг вызван после оплаты
{BOT_ID}, {BOT_NAME}, {BOT_TITLE}Бот
{MESSAGE_ID}, {MESSAGE_TITLE}Текущее API-сообщение
{CURRENT_DATE}, {CURRENT_TIME}, {CURRENT_DATETIME}Время в часовом поясе бота
{RESULT}, {RESULT.поле}, {RESULT_STATUS}Ответ предыдущего API-сообщения, если в цепочке их несколько

Пример фактического запроса

Настройки: POST https://api.example.com/bott/balance, Header Authorization: Bearer SECRET,
Query source=bot, Body telegram_id={USER_TELEGRAM_ID}, user.name={FIRST_NAME}.

curl -X POST 'https://api.example.com/bott/balance?source=bot' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer SECRET' \
  -H 'User-Agent: Bott-MessageApi/1.0' \
  -H 'X-Bott-Request-Id: 3f1c9b2e-7d1a-4a3b-9c1e-2b6f4d8a1c55' \
  -H 'X-Bott-Bot-Id: 42' \
  -H 'X-Bott-Message-Id: 1017' \
  -H 'X-Bott-User-Id: 5581' \
  -d '{"telegram_id":"777000000","user":{"name":"Alex"}}'

4. Как сформировать ответ

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

Правила

  1. Отдавайте JSON с Content-Type: application/json. Каждое поле станет плейсхолдером {RESULT.поле}; вложенность — через точку: {RESULT.data.user.name}. Элементы массива — по индексу: {RESULT.items.0.title}.
  2. HTTP-код = ветка сценария:
    • 200 — всё хорошо → «сообщение об успехе»;
    • 4xx (например 404 пользователь не найден, 400 промокод недействителен) → «сообщение об ошибке клиента». Не повторяется;
    • 5xx — сбой у вас → Bott повторит запрос, затем «сообщение об ошибке сервера».
  3. Идемпотентность. Повтор приходит с тем же X-Bott-Request-Id. Если действие не должно выполняться дважды (списание, создание заказа) — запомните ID и на повтор отдайте тот же ответ.
  4. Размер. В переменную попадают первые 60 000 символов тела; в лог ЛК — первые 8 KB. Не возвращайте HTML, файлы, большие списки.
  5. Время. 5 секунд на соединение, 20 секунд на ответ. Долгие операции выносите в фон и отвечайте сразу.
  6. Текст для пользователя удобно отдавать готовым полем — тогда в боте достаточно {RESULT.message}, а логику формулировок вы держите у себя.

Рекомендуемая форма

Успех (200):

{
  "ok": true,
  "message": "Ваш баланс: 1 250 ₽",
  "data": {
    "balance": 1250,
    "currency": "RUB",
    "level": "gold",
    "orders": [
      { "id": 501, "status": "paid" },
      { "id": 502, "status": "new" }
    ]
  }
}

Ошибка клиента (404):

{
  "ok": false,
  "error": "Пользователь не найден. Привяжите аккаунт на сайте",
  "code": "user_not_found"
}

Сбой сервера (500) — тело любое, Bott повторит запрос.

Если ответ не JSON

Тело целиком доступно как {RESULT}, а все {RESULT.поле} заменяются пустой строкой. Это допустимо для простых
случаев («ОК» / «Готово»), но лишает гибкости.


5. Как передать ответ в следующие сообщения Bott

Плейсхолдеры ответа

ПлейсхолдерЧто подставляетсяПример для ответа выше
{RESULT}Тело ответа целиком{"ok":true,"message":...}
{RESULT.путь}Поле JSON по пути через точку{RESULT.data.balance} → 1250, {RESULT.data.orders.0.id} → 501
{RESULT_STATUS}HTTP-код ответа200
{RESULT_REQUEST_ID}ID запроса (X-Bott-Request-Id)3f1c9b2e-…

Значения: строки и числа — как есть; true/false — словами; объект или массив — JSON-строкой;
отсутствующее поле или null — пустая строка.

Где работают

  • В тексте и в кнопках (подпись и URL) любого сообщения, идущего после API-сообщения в цепочке:
    сообщение ветки → его «следующее сообщение» → и далее, пока цепочка не закончится.
  • Переменная живёт в контексте одной отправки: если дальше в цепочке стоит другое API-сообщение,
    оно перезапишет {RESULT} своим ответом.
  • Цепочка прерывается на платёжном сообщении.

Пошагово

  1. Создайте API-сообщение, укажите URL, метод, параметры (telegram_id={USER_TELEGRAM_ID} в body).
  2. Откройте вкладку Тест → «Выполнить тестовый запрос». Увидите, что ушло на сервер и что он ответил.
  3. В дереве ответа нажмите на нужное поле — плейсхолдер (например {RESULT.data.balance}) скопируется.
  4. Создайте текстовое сообщение «Ваш баланс: {RESULT.data.balance} {RESULT.data.currency}. Уровень: {RESULT.data.level}» и прикрепите его на вкладке Сообщения как «Сообщение об успехе».
  5. Для ветки «ошибка клиента» создайте сообщение с текстом {RESULT.error} — сервер сам объяснит пользователю, что не так.
  6. К сообщению об успехе добавьте «следующее сообщение» с кнопкой «Мои заказы» и URL https://example.com/orders?user={USER_TELEGRAM_ID}&rid={RESULT_REQUEST_ID} — плейсхолдеры работают и там.

Что увидит пользователь при ответе из раздела 4:

Ваш баланс: 1250 RUB. Уровень: gold

6. Примеры приёмного сервера

Все три примера реализуют один и тот же endpoint POST /bott/balance:

  • читают X-Bott-Request-Id и Telegram ID из body;
  • дедуплицируют повторы по ID запроса;
  • отдают 200 с данными, 404 с текстом ошибки или 500 при сбое.

Рядом — тексты сообщений Bott, которые этот ответ выводят:

ВеткаТекст сообщения в Bott
Успех{RESULT.message}
Заказов: {RESULT.data.orders_count}, последний — №{RESULT.data.last_order.id}
Ошибка клиента⚠️ {RESULT.error}
Ошибка сервераСервис временно недоступен, попробуйте позже (запрос {RESULT_REQUEST_ID})

Python (Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

USERS = {"777000000": {"balance": 1250, "currency": "RUB", "orders": [{"id": 501}, {"id": 502}]}}
SEEN_REQUESTS: dict[str, tuple[dict, int]] = {}  # request_id -> (json, status); в бою — Redis/БД


@app.post("/bott/balance")
def balance():
    request_id = request.headers.get("X-Bott-Request-Id", "")
    if request_id in SEEN_REQUESTS:               # повтор от Bott — отдаём тот же ответ
        body, status = SEEN_REQUESTS[request_id]
        return jsonify(body), status

    payload = request.get_json(silent=True) or {}
    telegram_id = str(payload.get("telegram_id", ""))

    user = USERS.get(telegram_id)
    if user is None:                              # 4xx -> ветка «ошибка клиента», без повторов
        body, status = {"ok": False, "error": "Аккаунт не найден. Привяжите Telegram на сайте", "code": "user_not_found"}, 404
    else:                                         # 200 -> ветка «успех»
        body, status = {
            "ok": True,
            "message": f"Ваш баланс: {user['balance']} {user['currency']}",
            "data": {
                "balance": user["balance"],
                "currency": user["currency"],
                "orders_count": len(user["orders"]),
                "last_order": user["orders"][-1] if user["orders"] else None,
            },
        }, 200

    SEEN_REQUESTS[request_id] = (body, status)
    return jsonify(body), status
    # Любое необработанное исключение даст 500 -> Bott повторит запрос, затем ветка «ошибка сервера»

PHP (без фреймворка)

<?php
declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');

$requestId  = $_SERVER['HTTP_X_BOTT_REQUEST_ID'] ?? '';
$payload    = json_decode(file_get_contents('php://input'), true) ?: [];
$telegramId = (string)($payload['telegram_id'] ?? '');

$users = ['777000000' => ['balance' => 1250, 'currency' => 'RUB', 'orders' => [['id' => 501], ['id' => 502]]]];

// Дедупликация повторов: в бою — таблица/кэш с TTL, здесь файл для наглядности
$cacheFile = sys_get_temp_dir() . '/bott_' . preg_replace('/[^a-f0-9\-]/', '', $requestId) . '.json';
if ($requestId !== '' && is_file($cacheFile)) {
    $cached = json_decode(file_get_contents($cacheFile), true);
    http_response_code($cached['status']);
    echo json_encode($cached['body'], JSON_UNESCAPED_UNICODE);
    exit;
}

try {
    if (!isset($users[$telegramId])) {
        $status = 404; // ветка «ошибка клиента»
        $body = ['ok' => false, 'error' => 'Аккаунт не найден. Привяжите Telegram на сайте', 'code' => 'user_not_found'];
    } else {
        $user = $users[$telegramId];
        $status = 200; // ветка «успех»
        $body = [
            'ok' => true,
            'message' => sprintf('Ваш баланс: %d %s', $user['balance'], $user['currency']),
            'data' => [
                'balance' => $user['balance'],
                'currency' => $user['currency'],
                'orders_count' => count($user['orders']),
                'last_order' => $user['orders'] ? end($user['orders']) : null,
            ],
        ];
    }
} catch (Throwable $e) {
    $status = 500; // ветка «ошибка сервера», Bott повторит запрос
    $body = ['ok' => false, 'error' => 'internal'];
}

if ($requestId !== '' && $status !== 500) {
    file_put_contents($cacheFile, json_encode(['status' => $status, 'body' => $body]));
}

http_response_code($status);
echo json_encode($body, JSON_UNESCAPED_UNICODE);

JavaScript (Node.js + Express)

import express from 'express';

const app = express();
app.use(express.json());

const users = { '777000000': { balance: 1250, currency: 'RUB', orders: [{ id: 501 }, { id: 502 }] } };
const seenRequests = new Map(); // requestId -> { status, body }; в бою — Redis с TTL

app.post('/bott/balance', (req, res) => {
  const requestId = req.get('X-Bott-Request-Id') || '';
  if (seenRequests.has(requestId)) {
    const { status, body } = seenRequests.get(requestId); // повтор от Bott
    return res.status(status).json(body);
  }

  const telegramId = String(req.body?.telegram_id ?? '');
  const user = users[telegramId];

  let status;
  let body;
  if (!user) {
    status = 404; // ветка «ошибка клиента», без повторов
    body = { ok: false, error: 'Аккаунт не найден. Привяжите Telegram на сайте', code: 'user_not_found' };
  } else {
    status = 200; // ветка «успех»
    body = {
      ok: true,
      message: `Ваш баланс: ${user.balance} ${user.currency}`,
      data: {
        balance: user.balance,
        currency: user.currency,
        orders_count: user.orders.length,
        last_order: user.orders.at(-1) ?? null,
      },
    };
  }

  seenRequests.set(requestId, { status, body });
  res.status(status).json(body);
});

// Необработанная ошибка -> 500 -> Bott повторит запрос, затем ветка «ошибка сервера»
app.use((err, req, res, next) => res.status(500).json({ ok: false, error: 'internal' }));

app.listen(3000);

7. Ограничения

ОграничениеЗначение
ПротоколТолько https://. Адреса localhost, приватные и служебные IP отклоняются
Таймауты5 с на соединение, 20 с на ответ
РедиректыНе выполняются: 301/302 попадут в ветку «ошибка сервера» — отдавайте конечный URL
ПовторыТолько при сетевой ошибке и 5xx; пауза 0,5 с × номер попытки; максимум 5 повторов
Тело ответаВ переменную — до 60 000 символов, в лог — до 8 KB
Дедупликация BottОдин запрос на платёж (payment_history_id) в течение 5 минут
Заголовки-секреты в логахAuthorization, *-Key, *-Token, Cookie и т.п. маскируются

8. Диагностика

Вкладка Логи в настройках API-сообщения. Каждая запись:

  • статус (HTTP 200, HTTP 404, нет ответа), «тест» для тестовых запросов;
  • «попытка N из M» и длительность, если были повторы;
  • ID запроса с копированием — сопоставьте с логами вашего сервера по X-Bott-Request-Id;
  • «Подробнее»: адрес, заголовки (с маской), query, тело запроса, тело ответа, причина ошибки.

Типовые ситуации:

В логеПричинаЧто делать
адрес или прокси не прошли проверку безопасностиhttp://, локальный/приватный адресИспользовать публичный https://
не удалось подключиться к серверуDNS, закрытый порт, невалидный TLS-сертификатПроверить доступность извне и сертификат
превышено время ожидания ответаОтвет дольше 20 сУскорить обработку или отвечать сразу, работу делать в фоне
HTTP 301/302РедиректУказать конечный URL
HTTP 415/400 от вашего сервераНе принят JSONПроверить Content-Type и разбор тела
{RESULT.поле} пустой в ботеОтвет не JSON или путь неверныйСверить путь по дереву на вкладке «Тест»