RRGP Chat

RGP Chat — руководство для программистов

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

Все примеры кода из этого руководства лежат в папке examples/ и проверены на работающем чате.

1. Как это устроено

 Ваш сайт ──(подписанный билет)──▶  Ваш чат  ◀──(ключ API)── Ваши серверные системы
  (пользователь вошёл у вас)   chat.вашсайт.ru                (заказы, CRM, боты, уведомления)
                                       │
                                       └──(подписанные вебхуки)──▶ ваш сервер

У каждого чата платформы свой адрес, своя база данных и свои секреты. Три способа связи с вашими системами:

Что нужно Как Где используется
Пустить пользователя сайта в чат без пароля Билет входа (HMAC-подпись, живёт 2 минуты) Кнопка «Открыть чат» на вашем сайте
Писать в чат и читать его из ваших систем Ключ API (Authorization: Bearer rgpc_…) Сервер: уведомления, боты, интеграции
Узнавать о новых сообщениях сразу Вебхук (POST на ваш адрес, подпись HMAC) Сервер: модерация, CRM, пересылка

Секреты (секрет билетов, ключи API, секреты вебхуков) нельзя класть в браузерный код и в публичные репозитории.

2. Адрес чата

Рекомендуем поддомен вашего сайта: chat.вашсайт.ru (например chat.example.ru).

Причины:

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)

Правила:

Примеры

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 содержит созданное сообщение (как выше). Поля:

Участники получают 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"}

Как принимать:

  1. Прочитайте сырое тело запроса и посчитайте HMAC-SHA256 с секретом вебхука. Сравните с заголовком функцией сравнения за постоянное время. Не считайте подпись по разобранному и заново собранному JSON.
  2. Ответьте любым кодом 2xx в течение 8 секунд. Тяжёлую работу делайте после ответа.
  3. Учитывайте, что одно сообщение может прийти повторно. Храните обработанные 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. Безопасность

8. Что пока не умеет API 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 сообщений в минуту: объединяйте уведомления в одно