RGP Chat — руководство для программистов
Как подключить чат к своему сайту: впустить в него своих пользователей без второго пароля, отправлять сообщения из своих систем и получать новые сообщения у себя.
Все примеры кода из этого руководства лежат в папке examples/ и проверены на работающем чате.
1. Как это устроено
Ваш сайт ──(подписанный билет)──▶ Ваш чат ◀──(ключ API)── Ваши серверные системы
(пользователь вошёл у вас) chat.вашсайт.ru (заказы, CRM, боты, уведомления)
│
└──(подписанные вебхуки)──▶ ваш сервер
У каждого чата платформы свой адрес, своя база данных и свои секреты. Три способа связи с вашими системами:
| Что нужно | Как | Где используется |
|---|---|---|
| Пустить пользователя сайта в чат без пароля | Билет входа (HMAC-подпись, живёт 2 минуты) | Кнопка «Открыть чат» на вашем сайте |
| Писать в чат и читать его из ваших систем | Ключ API (Authorization: Bearer rgpc_…) |
Сервер: уведомления, боты, интеграции |
| Узнавать о новых сообщениях сразу | Вебхук (POST на ваш адрес, подпись HMAC) | Сервер: модерация, CRM, пересылка |
Секреты (секрет билетов, ключи API, секреты вебхуков) нельзя класть в браузерный код и в публичные репозитории.
2. Адрес чата
Рекомендуем поддомен вашего сайта: chat.вашсайт.ru (например chat.example.ru).
Причины:
- Вход по билету ставит cookie сессии на домен чата. Свой поддомен выглядит для пользователя как часть вашего сайта.
- Cookie сессии отдаются только по HTTPS (
Secure,SameSite=Lax), поэтому у чата обязательно должен быть сертификат. - Чат не рассчитан на показ внутри
<iframe>на чужом домене: браузеры не отправляютSameSite=Laxcookie во встроенных сторонних фреймах, вход там не сохранится. Делайте кнопку или ссылку, которая открывает чат в новой вкладке.
DNS: запись A для chat.вашсайт.ru на IP сервера, где живёт чат. Для чата на наших серверах мы сообщим IP при создании.
3. Вход пользователей сайта: билет
Пользователь уже вошёл на вашем сайте. Вы формируете подписанный билет и ведёте его по ссылке:
https://chat.вашсайт.ru/ticket_login.php?ticket=<БИЛЕТ>&to=/
Чат проверяет подпись и срок, открывает сессию и переводит пользователя на путь из to (только относительный путь на том же
домене). Вводить логин и пароль не нужно.
Формат билета
БИЛЕТ = base64url(JSON) + "." + base64url( HMAC-SHA256( СЕКРЕТ, base64url(JSON) ) )
base64url — стандартный base64 с заменой + на -, / на _ и без символов = в конце.
СЕКРЕТ — значение APP_TICKET_SECRET из файла secrets.php вашего чата.
Поля JSON:
| Поле | Тип | Обязательное | Смысл |
|---|---|---|---|
v |
число | да | Версия формата, всегда 1 |
aud |
строка | да | Для кого билет, всегда "chat" |
sub |
целое число > 0 | да | Постоянный id пользователя на вашем сайте. По нему чат узнаёт человека, его сообщения и права |
exp |
целое число | да | Время окончания в секундах Unix. Ставьте «сейчас + 120» |
name |
строка | рекомендуется | Имя в чате |
login |
строка | нет | Логин |
email |
строка | нет | Почта |
avatar_url |
строка | нет | Ссылка на аватар (https) |
Правила:
subдолжен быть одним и тем же числом для одного и того же человека всегда. Не используйте случайные числа и не переиспользуйте одинsubдля разных людей: так люди увидят чужую историю и права.- Если у вас текстовые id (uuid), заведите числовой суррогатный ключ или используйте автоинкремент таблицы пользователей.
- Билет не одноразовый: пока не истёк
exp, его можно использовать повторно. Поэтому срок делайте коротким и не показывайте ссылку третьим лицам. - Время на сервере вашего сайта должно быть точным (NTP). Расхождение больше пары минут даст «билет просрочен».
Примеры
Python (examples/sso_ticket.py):
import base64, hashlib, hmac, json, time
from urllib.parse import quote
def _b64(raw: bytes) -> str:
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
def make_ticket(secret, user_id, name, login="", ttl=120):
claims = {"v": 1, "aud": "chat", "sub": int(user_id), "exp": int(time.time()) + ttl, "name": name, "login": login}
payload = _b64(json.dumps(claims, ensure_ascii=False, separators=(",", ":")).encode())
sig = _b64(hmac.new(secret.encode(), payload.encode(), hashlib.sha256).digest())
return f"{payload}.{sig}"
link = f"https://chat.example.ru/ticket_login.php?ticket={quote(make_ticket(SECRET, 42, 'Иван Петров'))}&to=/"
PHP (examples/sso_ticket.php):
function chat_ticket(string $secret, int $userId, string $name, string $login = '', int $ttl = 120): string {
$b64 = fn(string $s) => rtrim(strtr(base64_encode($s), '+/', '-_'), '=');
$payload = $b64(json_encode(['v' => 1, 'aud' => 'chat', 'sub' => $userId, 'exp' => time() + $ttl,
'name' => $name, 'login' => $login], JSON_UNESCAPED_UNICODE));
return $payload . '.' . $b64(hash_hmac('sha256', $payload, $secret, true));
}
Node.js (examples/sso_ticket.js):
const crypto = require("crypto");
const b64 = (buf) => Buffer.from(buf).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
function chatTicket(secret, { userId, name, login = "", ttl = 120 }) {
const payload = b64(JSON.stringify({ v: 1, aud: "chat", sub: Number(userId), exp: Math.floor(Date.now() / 1000) + ttl, name, login }));
return payload + "." + b64(crypto.createHmac("sha256", secret).update(payload).digest());
}
Билет по другим языкам строится так же: JSON → base64url → HMAC-SHA256 от получившейся строки → base64url. Результаты всех трёх реализаций выше совпадают бит в бит.
Куда ведёт to
to=/ — главная страница чата. В to допустим только путь без параметров (?… в нём не сохраняется), поэтому прямой
ссылки в конкретную комнату билет не даёт: после входа пользователь выбирает комнату на вкладках.
Права и модераторы
Владелец чата — пользователь, чей sub указан как OWNER_AGENT_ID в config.php. Он назначает администраторов и
модераторов из интерфейса чата («Администрирование»). Остальные участники получают права обычных пользователей.
Ошибки входа
Если подпись неверна, билет просрочен или нет sub, чат возвращает пользователя на главную с параметром ?ticket_error=1.
Проверьте по очереди: секрет (без пробелов и переводов строки), aud, целое sub, exp в будущем, точность часов.
4. API для серверов (v1)
Базовый адрес: https://chat.вашсайт.ru/v1.php. Метод выбирается параметром r. Формат — JSON UTF-8.
Заголовок: Authorization: Bearer rgpc_…. Ключ можно ограничить правами (read, write) и списком комнат.
Ключ API
Создаёт владелец сервера, на котором живёт чат (на наших серверах — по вашей заявке):
cd /var/www/chat-<slug>/chat
sudo -u www-data php tools/chatctl.php create-key "Мой сайт" read,write
sudo -u www-data php tools/chatctl.php create-key "Только новости" read,write c_1,announce
sudo -u www-data php tools/chatctl.php list-keys
sudo -u www-data php tools/chatctl.php revoke-key 3
Ключ показывается один раз. В базе хранится только его хэш. Потерянный ключ не восстанавливается — отзовите и создайте новый.
GET v1.php?r=ping
Проверка ключа.
{"ok": true, "key": "Мой сайт", "scopes": "read,write", "rooms": "all"}
GET v1.php?r=rooms
Комнаты, доступные ключу.
{"rooms": [{"key": "c_1", "title": "Новости", "description": "Что нового"},
{"key": "announce", "title": "Объявления", "description": "…"}]}
GET v1.php?r=messages&room=KEY[&after_id=N][&limit=N]
Сообщения комнаты по возрастанию id. after_id — вернуть только новее этого id (для опроса: запоминайте next_after_id).
limit от 1 до 100 (по умолчанию 50). Скрытые и удалённые модераторами сообщения не отдаются.
{"messages": [{"id": 12, "room": "c_1", "author": "Иван Петров", "author_id": 42, "from_integration": false,
"body": "Добрый день!", "reply_to_id": null, "attachment_type": null, "attachment_url": null,
"created_at": "2026-10-12 18:51:16", "edited_at": null}],
"next_after_id": 12}
from_integration равно true, если сообщение написано через API. У таких сообщений author_id отрицательный (минус номер ключа).
Время created_at — в часовом поясе сервера базы данных чата.
POST v1.php?r=messages
Написать сообщение. Права write.
{"room": "c_1", "body": "Новый заказ №1234 оформлен", "author_name": "Магазин", "reply_to_id": null}
Ответ 201 содержит созданное сообщение (как выше). Поля:
room— ключ комнаты (обязательно).body— текст до 4000 символов (обязательно). Ссылки разрешены: ключу API доверяют.author_name— имя отправителя в ленте (до 100 символов). Если не задано, берётся название ключа.reply_to_id— id сообщения этой комнаты, на которое отвечаем.
Участники получают push-уведомление, как о обычном сообщении.
Ошибки
Ответ с ошибкой: {"error": "текст", "code": "код"}.
| HTTP | code |
Причина |
|---|---|---|
| 401 | unauthorized |
Нет ключа, ключ неверный или отозван |
| 403 | forbidden |
У ключа нет нужного права (read/write) |
| 404 | room_not_found |
Нет такой комнаты или она недоступна ключу |
| 404 | not_found |
Неизвестный метод |
| 400 | bad_json, empty, too_long |
Неверное тело, пустое сообщение, длиннее 4000 символов |
| 429 | rate_limited |
Больше 60 сообщений в минуту с одного ключа; повторите через время из заголовка Retry-After |
Примеры
Python (examples/api_client.py):
api = ChatApi("chat.example.ru", "rgpc_ВАШ_КЛЮЧ")
api.send("c_1", "Новый заказ №1234 оформлен", author_name="Магазин")
page = api.messages("c_1", after_id=0)
curl:
curl -s -X POST "https://chat.example.ru/v1.php?r=messages" \
-H "Authorization: Bearer rgpc_ВАШ_КЛЮЧ" -H "Content-Type: application/json" \
-d '{"room":"c_1","body":"Новый заказ №1234 оформлен","author_name":"Магазин"}'
PHP и Node.js — examples/api_client.php, examples/api_client.js.
5. Вебхуки: новое сообщение у вас на сервере
Вебхук — адрес на вашем сервере (только https://), куда чат присылает каждое новое сообщение. Работает для сообщений участников
и для сообщений, отправленных через API.
Создаётся так же, как ключ:
sudo -u www-data php tools/chatctl.php add-webhook https://example.ru/chat-hook # все комнаты
sudo -u www-data php tools/chatctl.php add-webhook https://example.ru/chat-hook c_1,c_2 # только эти комнаты
sudo -u www-data php tools/chatctl.php list-webhooks
sudo -u www-data php tools/chatctl.php remove-webhook 2
Секрет подписи показывается один раз при создании.
Запрос от чата:
POST /chat-hook
Content-Type: application/json
X-Chat-Signature: sha256=<hex HMAC-SHA256(секрет вебхука, ТЕЛО запроса)>
{"event":"message.created","room":"c_1",
"message":{"id":12,"room":"c_1","author":"Иван Петров","author_id":42,"from_integration":false,
"body":"Добрый день!","reply_to_id":null,"attachment_type":null,"attachment_url":null,
"created_at":"2026-10-12 18:51:16","edited_at":null},
"sent_at":"2026-10-12T15:51:16+00:00"}
Как принимать:
- Прочитайте сырое тело запроса и посчитайте HMAC-SHA256 с секретом вебхука. Сравните с заголовком функцией сравнения за постоянное время. Не считайте подпись по разобранному и заново собранному JSON.
- Ответьте любым кодом
2xxв течение 8 секунд. Тяжёлую работу делайте после ответа. - Учитывайте, что одно сообщение может прийти повторно. Храните обработанные
message.id.
Повторы: если ответ не 2xx, чат повторяет доставку до 6 раз с паузами 1, 5, 15, 60 и 240 минут. Очередь хранится 14 дней.
Чат сам вебхуки не отправляет, а кладёт в очередь: доставку выполняет планировщик cron/webhooks.php раз в минуту
(на наших серверах настроен; при самостоятельной установке добавьте строку в cron, см. SELF-HOST.md).
Примеры приёмника: examples/verify_webhook.py (Flask), examples/verify_webhook.php, examples/verify_webhook.js (Express).
6. Сценарии
«Открыть чат» в личном кабинете сайта. На сервере сайта по клику строите ссылку по разделу 3 и редиректите. Билет никогда не отдавайте в аналитику и не логируйте.
Уведомления о событиях. Заказ оформлен, оплата прошла, тикет создан: POST v1.php?r=messages с понятным author_name.
Бот поддержки. Опрашивайте GET messages с after_id или принимайте вебхук, отвечайте через POST messages с reply_to_id.
Пересылка в CRM. Вебхук на ваш сервер: сохраняйте message.id, автора и текст.
7. Безопасность
- Секрет билетов и ключи API держите в переменных окружения или менеджере секретов. Не коммитьте в репозиторий.
- Подозрение на утечку ключа API: отзовите (
revoke-key) и создайте новый. Утечка секрета билетов: сменитеAPP_TICKET_SECRETвsecrets.phpи у себя на сайте одновременно. Старые сессии пользователей останутся действовать до выхода. - Включите HTTPS везде. Вебхуки принимаются только на
https://. - Проверяйте подпись вебхука до разбора тела.
- Никогда не вызывайте API из браузера: ключ окажется у всех посетителей.
8. Что пока не умеет API v1
Сообщаем заранее, чтобы вы планировали интеграцию:
- Нет редактирования и удаления сообщений через API (модераторы делают это в интерфейсе чата).
- Нет загрузки файлов и картинок через API. Можно писать текст и ссылки.
- Нет событий кроме
message.created(нет правки, удаления, реакций). - Звонки и эфиры в чатах платформы пока недоступны: нужен отдельный медиасервер.
- Адрес метода задан параметром
?r=, а не путём/v1/…. Позже появится путь вида/v1/…, текущий останется работать.
9. Частые проблемы
| Симптом | Что проверить |
|---|---|
ticket_error=1 после перехода |
Секрет, целый положительный sub, aud:"chat", exp в будущем, время на сервере |
| Вход «не запоминается» | Чат открыт во встроенном <iframe> другого домена или по http://. Открывайте в новой вкладке по HTTPS |
401 unauthorized на API |
Ключ с лишними пробелами, отозван, не тот чат (у каждого чата свои ключи) |
404 room_not_found |
Ключ ограничен списком комнат или комнаты нет; запросите rooms |
| Вебхуки не приходят | Нет cron cron/webhooks.php; ваш адрес не https или отвечает дольше 8 секунд или не 2xx |
429 rate_limited |
Больше 60 сообщений в минуту: объединяйте уведомления в одно |