Введение
Introduction
Боты в X2Chat — это специальные аккаунты, которые управляются через HTTP API, а не вручную. Они умеют делать всё то же, что и обычные пользователи: отправлять сообщения, файлы, реагировать на команды, показывать клавиатуры с кнопками.
API на 95% совместим с Telegram Bot API — большинство клиентских библиотек (python-telegram-bot, node-telegram-bot-api, telegraf) можно использовать после смены базового URL.
Особенности X2Chat: сообщения от ботов — plaintext (бот не имеет приватного ключа для E2E-шифрования под получателей). Зашифрованный канал между пользователями остаётся E2E.
Bots in X2Chat are special accounts controlled via HTTP API, not by humans. They can do everything regular users do: send messages, files, react to commands, show button keyboards.
The API is 95% compatible with the Telegram Bot API — most client libraries (python-telegram-bot, node-telegram-bot-api, telegraf) work after switching the base URL.
X2Chat specifics: bot messages are plaintext (bots have no private key to do E2E encryption for recipients). User-to-user E2E remains intact.
Создание бота
Creating a bot
В X2Chat нет отдельного «BotFather» — управление ботами встроено прямо в приложение.
- Откройте приложение X2Chat и перейдите в «Мои боты» (URL:
/my-bots). - Нажмите «Новый бот».
- Введите
username— должен заканчиваться наbot(напримерweather_bot,my_cool_bot). - Укажите имя бота и описание.
- Получите токен в формате
123456789:AAEh…— сохраните его, он показывается только один раз.
Если токен утерян, его можно пересоздать в карточке бота. Старый токен перестанет работать сразу.
X2Chat has no separate "BotFather" — bot management is built into the app.
- Open X2Chat and go to "My Bots" (URL:
/my-bots). - Tap "New Bot".
- Enter
username— must end withbot(e.g.weather_bot,my_cool_bot). - Set display name and description.
- Receive the token in format
123456789:AAEh…— store it securely, shown only once.
If you lose the token, you can regenerate it in the bot card. The old one stops working immediately.
Авторизация
Authorization
Токен передаётся одним из трёх способов:
- В пути URL (как в Telegram):
/api/v1/bot<TOKEN>/method - Заголовок
X-Bot-Token: <TOKEN> - Заголовок
Authorization: Bot <TOKEN>
Все запросы к Bot API делаются на хост https://ix.x2chat.com (или https://ru.x2chat.com).
The token is passed in one of three ways:
- In URL path (Telegram-style):
/api/v1/bot<TOKEN>/method - Header
X-Bot-Token: <TOKEN> - Header
Authorization: Bot <TOKEN>
All Bot API requests go to https://ix.x2chat.com (or https://ru.x2chat.com).
Запросы и ответы
Requests & responses
Все запросы — HTTP POST с Content-Type: application/json (для getMe, getUpdates, getWebhookInfo, getMyCommands — GET).
Все ответы в формате Telegram Bot API:
All requests are HTTP POST with Content-Type: application/json (for getMe, getUpdates, getWebhookInfo, getMyCommands — use GET).
Responses follow the Telegram Bot API format:
Успех:
Success:
{ "ok": true, "result": { ... } }
Ошибка:
Error:
{ "ok": false, "error_code": 400, "description": "chat_id is required" }
Methods
getMe
Базовая информация о боте. Без параметров.
Basic info about the bot. No parameters.
$ curl https://ix.x2chat.com/api/v1/bot123456789:AAEh…/getMe
{
"ok": true,
"result": {
"id": "uuid",
"is_bot": true,
"username": "my_cool_bot",
"first_name": "My Cool Bot",
"can_join_groups": true,
"can_read_all_group_messages": false,
"supports_inline_queries": false
}
}
sendMessage
Отправить текстовое сообщение в чат. Бот должен быть участником чата.
Send a text message to a chat. The bot must be a member of the chat.
| Параметр | Parameter | Тип | Type | Описание | Description |
|---|---|---|---|---|---|
chat_id | integer | ID чата (обязательно)Chat ID (required) | |||
text | string | Текст, до 4096 символов (обязательно)Text, up to 4096 chars (required) | |||
parse_mode | string | «HTML», «MarkdownV2» (опционально)"HTML", "MarkdownV2" (optional) | |||
reply_to_message_id | string | UUID сообщения для ответаUUID of message to reply | |||
reply_markup | object | InlineKeyboard / Reply / Remove / ForceReplyInlineKeyboard / Reply / Remove / ForceReply | |||
disable_notification | bool | Без пушаSilent send |
$ curl -X POST https://ix.x2chat.com/api/v1/bot<TOKEN>/sendMessage \
-H "Content-Type: application/json" \
-d '{
"chat_id": 488,
"text": "Привет! Я бот.",
"reply_markup": {
"inline_keyboard": [[
{"text": "Yes", "callback_data": "yes"},
{"text": "No", "callback_data": "no"}
]]
}
}'
sendPhoto
Отправить картинку в чат (png/jpeg/gif/webp, до 10 МБ). Получатели видят её сразу — как обычное фото в чате.
Send an image to a chat (png/jpeg/gif/webp, up to 10 MB). Recipients see it instantly, like a regular photo.
| Параметр | Parameter | Тип | Type | Описание | Description |
|---|---|---|---|---|---|
chat_id | integer | ID чата (обязательно)Chat ID (required) | |||
photo | string | object | file_id ранее загруженного файла, https://… (сервер скачает сам), data:image/png;base64,… или {"base64": "…", "mime": "image/png"}file_id of a previously uploaded file, an https://… URL (fetched server-side), a data:image/png;base64,… string or {"base64": "…", "mime": "image/png"} | |||
caption | string | Подпись, до 1024 символовCaption, up to 1024 chars | |||
parse_mode | string | «HTML», «MarkdownV2» для подписи"HTML", "MarkdownV2" for the caption | |||
reply_markup | object | InlineKeyboard под картинкойInlineKeyboard under the photo | |||
disable_notification | bool | Без пушаSilent send |
Ответ: photo: [{file_id, file_size, width, height}] — file_id можно переиспользовать в следующих sendPhoto без повторной загрузки.
Response: photo: [{file_id, file_size, width, height}] — reuse file_id in later sendPhoto calls without re-uploading.
$ curl -X POST https://ix.x2chat.com/api/v1/bot<TOKEN>/sendPhoto \
-H "Content-Type: application/json" \
-d '{
"chat_id": 488,
"photo": "https://example.com/scene.webp",
"caption": "Маяк в тумане"
}'
sendChatAction
Показать участникам чата «<имя бота> печатает…». Индикатор гаснет через ~3 с — повторяйте вызов, пока бот готовит ответ.
Show "<bot name> is typing…" to chat members. The indicator fades after ~3 s — repeat the call while the bot prepares its reply.
| Параметр | Parameter | Тип | Type | Описание | Description |
|---|---|---|---|---|---|
chat_id | integer | ID чата (обязательно)Chat ID (required) | |||
action | string | typing (по умолчанию), upload_photo, upload_document, upload_video, record_voicetyping (default), upload_photo, upload_document, upload_video, record_voice |
editMessageText
Заменить текст уже отправленного сообщения.
Replace text of an already-sent message.
{ "chat_id": 488, "message_id": "<uuid>", "text": "Обновлённый текст" }
deleteMessage
{ "chat_id": 488, "message_id": "<uuid>" }
forwardMessage
Бот должен быть участником обоих чатов.
Bot must be a member of both chats.
{ "chat_id": 488, "from_chat_id": 476, "message_id": "<uuid>" }
getUpdates
Long-polling: получить новые updates. Используйте если не настроили webhook.
Long polling: receive new updates. Use this if no webhook is set.
{
"ok": true,
"result": [
{ "update_id": 1, "message": { ... } },
{ "update_id": 2, "callback_query": { ... } }
]
}
setWebhook
Зарегистрировать URL, на который X2Chat будет POST'ить updates. После регистрации webhook — getUpdates перестаёт получать новые updates.
Register a URL to which X2Chat will POST updates. Once a webhook is set, getUpdates will no longer return new updates.
{
"url": "https://your-server.com/x2chat-webhook",
"secret_token": "random_secret_for_x_telegram_header",
"allowed_updates": ["message", "callback_query"],
"max_connections": 40
}
setMyCommands
{
"commands": [
{ "command": "start", "description": "Начать работу" },
{ "command": "help", "description": "Справка" }
]
}
answerCallbackQuery
Подтвердить нажатие на InlineKeyboard-кнопку. text покажется как тост или alert у пользователя.
Acknowledge a tap on an InlineKeyboard button. text is shown as toast or alert to the user.
{
"callback_query_id": "<id>",
"text": "Спасибо за ответ!",
"show_alert": false,
"effect": "confetti"
}
effect (необязательно): haptic · sound · confetti · shake — отклик на нажатие, клиент воспроизводит его при получении ответа. Неизвестное значение игнорируется.
effect (optional): haptic · sound · confetti · shake — tap feedback played by the client. Unknown values are ignored.
editMessageReplyMarkup
Заменить или убрать клавиатуру у своего сообщения. Без reply_markup (или null) — клавиатура удаляется, старые кнопки перестают быть кликабельными. Все устройства получают обновление сразу. Персонаж и оформление пузыря сохраняются.
Replace or remove the keyboard of your own message. Omitting reply_markup (or null) removes it so stale buttons stop working. All devices update live. Persona and bubble styling are kept.
{
"chat_id": 123,
"message_id": "<uuid>",
"reply_markup": { "inline_keyboard": [[{ "text": "Дальше", "callback_data": "next", "style": "primary" }]] }
}
editMessageText тоже принимает reply_markup, as, bubble; если поле не передано — остаётся прежним (клавиатура не снимается). Редактировать можно только сообщения этого бота.
editMessageText also accepts reply_markup, as, bubble; omitted fields stay unchanged (the keyboard is not removed). Only this bot's own messages can be edited.
setMyTheme · getMyTheme · deleteMyTheme
Тема всего чата с ботом: акцент, фон (цвет, картинка https или градиент из 2–3 цветов), пузыри, кнопки по умолчанию. Не чаще 1 раза в минуту (иначе 429 с parameters.retry_after). getMyTheme возвращает сохранённую тему (или null), deleteMyTheme возвращает тему приложения. Пока хранилище темы не включено на сервере, методы отвечают 503.
Theme for the whole chat with the bot: accent, background (color, https image or 2–3 color gradient), bubbles, default buttons. At most once per minute (otherwise 429 with parameters.retry_after). getMyTheme returns the stored theme (or null), deleteMyTheme restores the app theme. Until theme storage is enabled on the server these methods return 503.
{
"theme": {
"accent": "#1E88E5",
"background": { "gradient": ["#0D1B2A", "#1B263B"] },
"bubble": { "color": "#1B263B", "text_color": "#E0E1DD" },
"button_defaults": { "style": "secondary", "shape": "pill" }
}
}
setMyDescription · setMyShortDescription · getMyDescription · getMyShortDescription
{"description": "…"} (≤ 512) — текст в пустом чате и в профиле бота; {"short_description": "…"} (≤ 120) — «о боте». Пустое значение очищает.
{"description": "…"} (≤ 512) — shown in an empty chat and the bot profile; {"short_description": "…"} (≤ 120) — the "about" line. Empty value clears it.
setMyStartButton · getMyStartButton
Пользователю не нужно набирать /start. Пока он ничего не написал боту, вместо поля ввода приложение показывает большую кнопку; тап отправляет /start (или /start <payload>) обычным сообщением — бот получает его как набранный текст. text ≤ 32 (по умолчанию «Начать» на языке пользователя), payload — до 64 символов A-Z a-z 0-9 _ -. null — сброс.
Users never type /start. Until they send anything, the app shows a big button instead of the composer; a tap sends /start (or /start <payload>) as a normal message — the bot receives it exactly like typed text. text ≤ 32 (default: localized "Start"), payload — up to 64 of A-Z a-z 0-9 _ -. null resets.
{ "start_button": { "text": "Начать квест", "payload": "lighthouse" } }
Меню команд: рядом с полем ввода кнопка «Меню» со списком из setMyCommands (описания на языке пользователя — передайте language_code на верхнем уровне, как в Telegram); тап отправляет команду сообщением.
Commands menu: a "Menu" button next to the composer lists setMyCommands entries (descriptions in the user's language — pass top-level language_code, as in Telegram); a tap sends the command as a message.
setMyMenu · getMyMenu · deleteMyMenu
Своё меню бота вместо простого списка команд и быстрые кнопки у поля ввода (composer_buttons, до 6). Пункт: id, text ≤ 48, description ≤ 96, icon, style/цвета (контраст исправляется), action. Действия: command (отправить /команду), text (отправить текст), callback (callback_query боту с message: null и source: "menu"; принимается, только если data есть в сохранённом меню), url и web_app (только https), submenu, share. До 50 пунктов, вложенность до 3. Варианты по scope ("default" или {"type":"chat","chat_id":…}) и language_code. getMyMenu/deleteMyMenu принимают те же scope/language_code. Пока хранилище меню не включено на сервере — 503.
A custom bot menu instead of the plain commands list, plus quick buttons by the composer (composer_buttons, up to 6). Item: id, text ≤ 48, description ≤ 96, icon, style/colors (contrast auto-corrected), action. Actions: command (send /command), text (send text), callback (callback_query to the bot with message: null and source: "menu"; accepted only if data exists in the saved menu), url and web_app (https only), submenu, share. Up to 50 items, depth up to 3. Variants by scope ("default" or {"type":"chat","chat_id":…}) and language_code. getMyMenu/deleteMyMenu take the same scope/language_code. Until menu storage is enabled on the server — 503.
{
"language_code": "ru",
"menu": {
"button": { "text": "Квест", "icon": { "emoji": "🧭" } },
"layout": { "type": "grid", "columns": 2 },
"items": [
{ "id": "restart", "text": "Начать заново", "action": { "type": "command", "command": "restart" } },
{ "id": "inv", "text": "Инвентарь", "action": { "type": "callback", "data": "inv" } }
]
},
"composer_buttons": [
{ "text": "Осмотреться", "action": { "type": "text", "text": "Осмотреться" } }
]
}
Типы updates
Update types
| Type | Когда | When |
|---|---|---|
message | Пользователь отправил сообщение в чат, где бот — участник. Включая /команды.User sent a message in a chat where the bot is a member. Includes /commands. | |
callback_query | Пользователь нажал на InlineKeyboard-кнопку с callback_data.User tapped an InlineKeyboard button with callback_data. |
Inline-клавиатура
Inline keyboard
Передайте reply_markup в sendMessage. Формат — 2D массив объектов кнопок:
Pass reply_markup to sendMessage. The format is a 2D array of button objects:
{
"inline_keyboard": [
[
{ "text": "👍 Like", "callback_data": "like" },
{ "text": "👎 Dislike", "callback_data": "dislike" }
],
[
{ "text": "📝 Open page", "url": "https://example.com" }
]
]
}
Поля кнопки:
Button fields:
| Field | Type | Описание | Description |
|---|---|---|---|
text | string | Подпись (обязательно)Label (required) | |
callback_data | string | Произвольная строка ≤ 64 байта. Доставляется боту как callback_query.Arbitrary string ≤ 64 bytes. Delivered to bot as callback_query. | |
url | string | Открывается внешним приложениемOpens in external app | |
switch_inline_query | string | Заполняет input bar пользователяFills user's input bar | |
send_text | bool | string | Расширение X2Chat. По тапу текст (true — подпись кнопки) уходит в чат как обычное сообщение пользователя и приходит боту как message. Быстрые ответы для квестов и опросов: переписка выглядит как настоящий диалог. Если есть ещё и callback_data — придёт и callback_query.X2Chat extension. On tap the text (true — the button label) is sent to the chat as the user's own message and reaches the bot as a message. Quick replies for quests and polls: the history reads like a real dialogue. With callback_data present, a callback_query is delivered too. |
Команды
Commands
Когда пользователь отправляет в чат сообщение /start или /help@my_cool_bot, X2Chat:
- Парсит команду из первого слова
- Если указан
@username— отправляет только этому боту - Иначе — всем ботам, подписанным на чат
- Update приходит как
messageсentities=[{"type": "bot_command", "offset": 0, "length": N}]
Команды видны пользователю через «меню команд» (UI чата). Подсветка команд использует setMyCommands.
When a user sends /start or /help@my_cool_bot, X2Chat:
- Parses the command from the first word
- If
@usernameis specified — delivers only to that bot - Otherwise — delivers to all bots subscribed to the chat
- Update arrives as
messagewithentities=[{"type": "bot_command", "offset": 0, "length": N}]
Commands appear to the user via the command menu (chat UI). The menu uses setMyCommands for descriptions.
Examples
Node.js
import fetch from 'node-fetch';
const TOKEN = '123456789:AAEh…';
const BASE = `https://ix.x2chat.com/api/v1/bot${TOKEN}`;
async function sendMessage(chatId, text, replyMarkup) {
const r = await fetch(`${BASE}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chatId, text, reply_markup: replyMarkup }),
});
return r.json();
}
async function poll() {
let offset = 0;
while (true) {
const resp = await fetch(`${BASE}/getUpdates?offset=${offset}&limit=100`);
const { result } = await resp.json();
for (const u of result) {
offset = Math.max(offset, u.update_id);
if (u.message?.text === '/start') {
await sendMessage(u.message.chat.id, 'Hello!');
}
}
if (result.length === 0) await new Promise(r => setTimeout(r, 1000));
}
}
poll();
Python (requests)
import requests, time
TOKEN = '123456789:AAEh…'
BASE = f'https://ix.x2chat.com/api/v1/bot{TOKEN}'
def send_message(chat_id, text, reply_markup=None):
return requests.post(f'{BASE}/sendMessage', json={
'chat_id': chat_id, 'text': text,
'reply_markup': reply_markup,
}).json()
offset = 0
while True:
r = requests.get(f'{BASE}/getUpdates', params={'offset': offset, 'limit': 100}).json()
for u in r['result']:
offset = max(offset, u['update_id'])
if u.get('message', {}).get('text') == '/start':
send_message(u['message']['chat']['id'], 'Привет!')
if not r['result']: time.sleep(1)
Простой webhook (Express)
Simple webhook (Express)
import express from 'express';
const app = express();
app.use(express.json());
app.post('/x2chat-webhook', (req, res) => {
const update = req.body;
if (update.callback_query) {
const { id, data } = update.callback_query;
// answer back
fetch(`https://ix.x2chat.com/api/v1/bot<TOKEN>/answerCallbackQuery`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ callback_query_id: id, text: `Получено: ${data}` }),
});
}
res.json({ ok: true });
});
app.listen(3000);
Оформление сообщений и кнопок (UI v2)
Message & button styling (UI v2)
Бот сам задаёт вид кнопок, персонажей и пузырей — приложение рисует то, что пришло, без обновлений. Всё необязательно: приложения, которые ещё не умеют это рисовать, показывают обычные кнопки и текст. Полная схема: bot-ui-schema.v1.json (в репозитории docs/). Поддерживаемые возможности сервер перечисляет в getMe → ui_features.
Кнопка: style (primary · secondary · success · danger · ghost · link), color / text_color / border_color (#RRGGBB или #RRGGBBAA), shape (rounded · pill · square), size (s · m · l), icon ({"emoji"} · {"symbol"} — имя Material Symbols · {"url"} https), width (auto · full), disabled, selected, badge (≤ 8).
Клавиатура: layout {columns 1–8, gap, align}; one_time: true — после первого нажатия кнопки исчезают на всех устройствах (повторное нажатие → 409); с selected_callback: true клавиатура остаётся, нажатая кнопка подсвечена, остальные неактивны.
Персонаж as: {name ≤ 32, avatar_url https | avatar_emoji, color} — имя и аватар на пузыре; приложение всегда показывает, что это бот и какой. Служебные имена (X2Chat, Support, System…) запрещены.
Пузырь bubble: {color, text_color, accent}.
Контраст исправляется автоматически. Текст кнопки и пузыря — не меньше 4.5:1 к своему фону (с учётом прозрачности), фон или рамка кнопки должны быть различимы (≥ 1.5:1) и на светлой, и на тёмной теме. Если нет — сервер детерминированно затемняет/осветляет цвет или добавляет рамку. Что поправлено или отброшено, возвращается в поле warnings ответа. Жёсткие нарушения (больше 100 кнопок, больше 8 в ряд, callback_data длиннее 64 байт) — ошибка 400. Неизвестные поля молча отбрасываются.
The bot controls how buttons, characters and bubbles look — the app renders what it receives, no app updates needed. Everything is optional: apps that cannot render it yet fall back to plain buttons and text. Full schema: bot-ui-schema.v1.json (repo docs/). The server lists supported features in getMe → ui_features.
Button: style (primary · secondary · success · danger · ghost · link), color / text_color / border_color (#RRGGBB or #RRGGBBAA), shape (rounded · pill · square), size (s · m · l), icon ({"emoji"} · {"symbol"} Material Symbols name · {"url"} https), width (auto · full), disabled, selected, badge (≤ 8).
Keyboard: layout {columns 1–8, gap, align}; one_time: true — after the first tap buttons disappear on every device (a second tap → 409); with selected_callback: true the keyboard stays, the tapped button is highlighted and all are disabled.
Persona as: {name ≤ 32, avatar_url https | avatar_emoji, color} — name and avatar on the bubble; the app always shows that it is a bot and which one. Service names (X2Chat, Support, System…) are rejected.
Bubble bubble: {color, text_color, accent}.
Contrast is auto-corrected. Button and bubble text must reach 4.5:1 against its (alpha-composited) background, and a button's background or border must stay visible (≥ 1.5:1) on both light and dark themes. Otherwise the server deterministically darkens/lightens the color or adds a border. Everything corrected or dropped is reported in the response warnings. Hard violations (over 100 buttons, over 8 per row, callback_data over 64 bytes) return 400. Unknown fields are silently dropped.
POST /api/v1/bot<TOKEN>/sendMessage
{
"chat_id": 123,
"text": "Маяк мигает. Что делаем?",
"as": { "name": "Смотритель", "avatar_emoji": "🧙" },
"reply_markup": {
"one_time": true,
"inline_keyboard": [[
{ "text": "Идти", "callback_data": "go", "color": "#FFFFFF", "text_color": "#FFFFFF" }
]]
}
}
→ {
"ok": true,
"result": { "message_id": "…", "as": { "name": "Смотритель", "is_persona": true, … }, "reply_markup": { … } },
"warnings": [
"button[0][0]: text_color #FFFFFF → #737373 (contrast < 4.5:1)",
"button[0][0]: border_color (none) → #CCCCCC (button invisible on chat bubble)"
]
}
Лимиты и особенности
Limits & specifics
| Параметр | Parameter | Лимит | Limit |
|---|---|---|---|
| Длина текстового сообщенияText length | 4096 chars | ||
| Длина callback_datacallback_data length | 64 bytes | ||
| Кнопок в строкеButtons per row | 8 | ||
| Строк в клавиатуреRows in keyboard | 100 | ||
| Команд в setMyCommandssetMyCommands commands | 100 | ||
| TTL callback_query (для answerCallbackQuery)callback_query TTL | 5 min | ||
| Webhook retriesWebhook retries | fire-and-forget, нетfire-and-forget, none |
⚠ Важно: сообщения от ботов не зашифрованы E2E. Не отправляйте через бота приватные данные пользователей.
⚠ Important: bot messages are not E2E-encrypted. Don't send sensitive user data through a bot.
✓ Поддерживается: getMe, sendMessage, editMessageText, deleteMessage, forwardMessage, getUpdates, setWebhook/getWebhookInfo/deleteWebhook, setMyCommands/getMyCommands, answerCallbackQuery, sendPhoto, sendChatAction, InlineKeyboard (url/callback_data/switch_inline_query/send_text), /command routing с @username.
✓ Supported: getMe, sendMessage, editMessageText, deleteMessage, forwardMessage, getUpdates, setWebhook/getWebhookInfo/deleteWebhook, setMyCommands/getMyCommands, answerCallbackQuery, sendPhoto, sendChatAction, InlineKeyboard (url/callback_data/switch_inline_query/send_text), /command routing with @username.
В разработке: sendDocument/sendVideo/sendVoice, ReplyKeyboard, inline_query (@bot mode), кастомные аватарки бота, статистика.
Coming soon: sendDocument/sendVideo/sendVoice, ReplyKeyboard, inline_query (@bot mode), custom bot avatars, statistics.
🆕 One-click QR (два направления):
- Integration QR — X2Chat → ваш сайт. Юзер делает QR в X2Chat, ваш сайт сканит и получает bot_token + chat_id автозаполнением.
- Webhook QR — ваш сайт → X2Chat. Сайт показывает QR с webhook URL, X2Chat сканит и настраивает бота на ваш endpoint.
🆕 One-click QR (two directions):
- Integration QR — X2Chat → your site. User makes a QR in X2Chat, your site scans it and gets bot_token + chat_id by autofill.
- Webhook QR — your site → X2Chat. Site shows QR with webhook URL, X2Chat scans and configures the bot to call your endpoint.
© X2Chat · Поддержка: bots@x2chat.com
© X2Chat · Support: bots@x2chat.com