API сообщений: push-уведомления сотрудникам от ваших систем
1С, касса, CRM, мониторинг или любой ваш сервис отправляет короткое сообщение — сотрудник получает push в приложении «Рация», даже если оно закрыто. Один HTTP-запрос, без SDK. База: https://lab.onecdp.ru · JSON, UTF-8.
Быстрый старт
1. Получите ключ отправителя. Ключ (sp_live_…) выдаёт владелец платформы при заведении вашего отправителя — вместе с именем, которое сотрудники увидят как автора сообщений (например «Бухгалтерия 1С»). Ключ показывается один раз — сохраните его в секретах.
2. Отправьте первое сообщение на свой рабочий номер:
curl -X POST https://lab.onecdp.ru/messages \
-H "Authorization: Bearer sp_live_ВАШ_КЛЮЧ" \
-H "Content-Type: application/json" \
-d '{"to": "+79991234567", "title": "Тест", "body": "Интеграция работает"}'3. Проверьте телефон. Push придёт в течение секунд; сообщение появится и во вкладке «Уведомления» приложения. Ответ 202 с messageId — запрос принят.
Модель: отправитель и получатель
Отправитель — ваша система. У неё есть apiKey (аутентификация) и имя (его видит сотрудник как автора). Имя фиксируется при заведении отправителя и в запросах не передаётся.
Получатель — сотрудник, всегда задаётся номером телефона в формате E.164 (+79991234567). Пробелы, скобки и дефисы допустимы — сервер нормализует.
Сообщение уйдёт только сотруднику из корпоративного справочника компании на платформе ClientPulse — тот же контроль доступа, что у звонков. Уволили сотрудника → убрали из справочника → доставка прекращается, интеграцию менять не нужно. Если ни один получатель не прошёл проверку, запрос вернёт 403.
Несколько отправителей — по одному на систему
У организации может быть сколько угодно отправителей, и это рекомендуемый паттерн: одна система — один отправитель. Сотрудник видит, кто именно пишет («Бухгалтерия 1С», «Мониторинг», «Склад WMS»), и уведомления каждой системы живут отдельным тредом — ничего не смешивается.
- Свой ключ у каждой системы: скомпрометирован один — отзывается один, остальные работают.
- Свой лимит 60 сообщений/мин на отправителя — «шумная» система не съедает квоту остальных.
- Идемпотентность считается на отправителя — ключи событий разных систем не конфликтуют.
Транспорт и время доставки
Доставка построена по той же двухканальной схеме, что у крупных мессенджеров: пока приложение открыто, сообщение приходит по постоянному соединению; спящее приложение будит системный push — APNs на iOS, FCM на Android. Серверная часть при этом полностью своя — без сторонних облаков-посредников.
| Состояние устройства | Канал | Типичная задержка |
|---|---|---|
открыто | постоянное соединение | десятки миллисекунд |
свёрнуто / закрыто | APNs / FCM push | 1–5 секунд |
вне сети | push при появлении сети + синхронизация истории | при следующем подключении |
Порядок жёсткий: сообщение сначала записывается в историю сотрудника и только потом доставляется (history-first). Поэтому «потерянных» сообщений не бывает: даже если телефон был выключен и push не дошёл, сообщение появится при следующем запуске приложения. История хранится 30 дней.
Отправка: POST /messages
Заголовки
Authorization: Bearer sp_live_… Content-Type: application/json
Тело запроса
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
to | string | string[] | "all" | да | Номер, массив номеров (E.164) или «all» — всем сотрудникам справочника. |
title | string | нет* | Заголовок, до 100 символов. Лишнее обрезается. |
body | string | нет* | Текст, до 500 символов. Поддерживает кликабельные ссылки и переносы строк \n. |
priority | "normal" | "high" | нет | По умолчанию normal. high — для срочного (инцидент, стоп-лист). |
idempotencyKey | string | нет | Защита от дублей: повтор с тем же ключом в течение 24 ч → 409. |
* хотя бы одно из title/body должно быть непустым.
Ответ 202 Accepted
{
"status": 202,
"messageId": "msg_mrn8k2_a41f9c07",
"recipients": 3, // валидных получателей принято в обработку
"delivered": 1, // доставлено в открытое приложение прямо сейчас
"pushed": 2 // отправлено push-уведомлением (устройство спало)
}Гарантия доставки: сообщение сначала записывается в историю сотрудника и только потом доставляется — даже если телефон был выключен, сотрудник увидит сообщение при следующем запуске приложения. delivered/pushed — телеметрия момента отправки; проверять их в интеграции не нужно.
Кликабельные ссылки в тексте
В body работает markdown-формат [подпись](https://…) — в приложении подпись становится кликабельной, а в push-баннере сворачивается до чистого текста. «Голые» https://… распознаются автоматически.
"body": "ООО «М-Лайн», 10 699,50 ₽. В т.ч. НДС 5%.\n[Документ №37574](https://mbill.example/doc/37574)"
Идемпотентность и повторы
Если ваша система ретраит запросы, передавайте idempotencyKey, привязанный к бизнес-событию: invoice-37574, order-9912-created. Повтор с тем же ключом в течение 24 часов вернёт 409 duplicate, и сообщение не задублируется. Рекомендуемые ретраи: сетевые ошибки и 5xx — с экспоненциальной задержкой (1 с → 5 с → 30 с); 409 считать успехом; при 429 подождать минуту.
Ошибки и лимиты
| Код | Тело | Что значит и что делать |
|---|---|---|
401 | unauthorized | Нет или неверный apiKey. Проверьте заголовок Authorization. |
400 | empty_message | Пустые title и body одновременно. |
400 | no_recipients | Поле to пустое или ни один номер не распознан как телефон. |
403 | no_valid_recipients | Ни один номер не найден в справочнике. Проверьте, что сотрудник заведён и активен. |
409 | duplicate | Повтор idempotencyKey за 24 ч. Для ретраев это успех — сообщение уже отправлено. |
429 | rate_limited | Больше 60 сообщений в минуту от вашего отправителя. Подождите и повторите. |
| Лимит | Значение |
|---|---|
title | 100 символов (лишнее обрезается) |
body | 500 символов (лишнее обрезается) |
частота | 60 сообщений/мин на отправителя |
история | 30 дней, последние 200 сообщений на сотрудника |
вложения | нет — канал текстовый, односторонний |
Примеры кода
curl
curl -X POST https://lab.onecdp.ru/messages \
-H "Authorization: Bearer $RATSIYA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": ["+79991234567", "+79997654321"],
"title": "Оплата по счёту №37574",
"body": "ООО «М-Лайн», 10 699,50 ₽.\n[Документ №37574](https://mbill.example/doc/37574)",
"priority": "high",
"idempotencyKey": "invoice-37574-paid"
}'1С:Предприятие 8.3
Функция ОтправитьВРацию(Номер, Заголовок, Текст, КлючСобытия = "")
Соединение = Новый HTTPСоединение("lab.onecdp.ru", 443, , , ,
, Новый ЗащищенноеСоединениеOpenSSL);
Тело = Новый Структура("to, title, body", Номер, Заголовок, Текст);
Если ЗначениеЗаполнено(КлючСобытия) Тогда
Тело.Вставить("idempotencyKey", КлючСобытия);
КонецЕсли;
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, Тело);
Запрос = Новый HTTPЗапрос("/messages");
Запрос.Заголовки.Вставить("Authorization", "Bearer " + КонстантыКлючРации());
Запрос.Заголовки.Вставить("Content-Type", "application/json");
Запрос.УстановитьТелоИзСтроки(ЗаписьJSON.Закрыть());
Ответ = Соединение.ОтправитьДляОбработки(Запрос);
// 202 — принято; 409 — уже отправляли (для ретраев это успех)
Возврат Ответ.КодСостояния = 202 Или Ответ.КодСостояния = 409;
КонецФункцииPHP
function sendRatsiya(string|array $to, string $title, string $body, ?string $idempotencyKey = null): ?array
{
$payload = ['to' => $to, 'title' => $title, 'body' => $body];
if ($idempotencyKey !== null) {
$payload['idempotencyKey'] = $idempotencyKey;
}
$ch = curl_init('https://lab.onecdp.ru/messages');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('RATSIYA_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status === 409) return null; // дубль — уже отправлено
if ($status !== 202) throw new RuntimeException("ratsiya: HTTP $status");
return json_decode($response, true); // ['messageId' => …]
}
sendRatsiya('+79991234567', 'Новый заказ №9912',
"Сборка до 15:00.\n[Открыть заказ](https://wms.example/o/9912)",
'order-9912-created');Python
import os, requests
def send_ratsiya(to, title, body, key=None, priority="normal"):
r = requests.post(
"https://lab.onecdp.ru/messages",
headers={"Authorization": f"Bearer {os.environ['RATSIYA_API_KEY']}"},
json={"to": to, "title": title, "body": body,
"priority": priority,
**({"idempotencyKey": key} if key else {})},
timeout=10,
)
if r.status_code in (202, 409): # 409 = дубль, уже отправлено
return r.json() if r.status_code == 202 else None
r.raise_for_status()
send_ratsiya("+79991234567", "Новый заказ №9912",
"Сборка до 15:00.\n[Открыть заказ](https://wms.example/o/9912)",
key="order-9912-created")Node.js
async function sendRatsiya(to, title, body, idempotencyKey) {
const res = await fetch("https://lab.onecdp.ru/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.RATSIYA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ to, title, body, idempotencyKey }),
});
if (res.status === 409) return null; // дубль — уже отправлено
if (res.status !== 202) throw new Error(`ratsiya: HTTP ${res.status}`);
return res.json(); // { messageId, recipients, … }
}Чек-лист интеграции
- apiKey хранится в секретах/конфигурации сервера, не в коде и не на клиенте.
- Номера получателей — E.164 из вашей учётной системы; получатели заведены в справочнике компании.
- Каждое бизнес-событие шлётся с idempotencyKey — ретраи не дублируют уведомления.
- Обработаны 409 (успех) и 429 (подождать минуту); на 5xx — ретрай с backoff.
- priority: high используется только для действительно срочного.
- Тест на своём номере пройден: push пришёл, ссылка в сообщении открывается.
Ключ отправителя выдаёт владелец платформы при подключении услуги — оставьте заявку на странице «Рации». Приложение сотрудникам: скачать для Android и iPhone →