API для отправки SMS через ваш Android-телефон

REST API для отправки SMS с ваших Android-телефонов из любой CRM, сайта или своей системы. SMS уходят с SIM-карт телефонов, подключённых к аккаунту, ответы клиентов и статусы доставки приходят вебхуками. Отправка через API учитывается в лимите тарифа так же, как из Битрикс24.

Базовый адрес: https://smstrubka.ru/api/v1 · Формат: JSON, UTF-8 · Спецификация: OpenAPI 3 (openapi.json) — можно импортировать в Postman или Swagger.
  1. Быстрый старт
  2. Авторизация и лимиты
  3. Ошибки
  4. Отправить SMS
  5. Статусы сообщений
  6. Журнал и статус сообщения
  7. Телефоны и аккаунт
  8. Вебхуки
  9. Проверка подписи
  10. Советы

Быстрый старт

  1. Подключите Android-телефон в личном кабинете (раздел «Телефон»).
  2. Создайте ключ в разделе «API». Ключ показывается один раз.
  3. Отправьте SMS:
curl https://smstrubka.ru/api/v1/messages \
  -H "Authorization: Bearer trk_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"phone": "+79001234567", "text": "Ваш заказ готов", "clientRef": "order-1043"}'

Ответ 202 Accepted — SMS передано телефону:

{
  "message": {
    "id": "7c0b8f3e-2d1a-4a7e-9f43-0f6c2a1b5d90",
    "direction": "out",
    "phone": "+79001234567",
    "text": "Ваш заказ готов",
    "status": "queued",
    "error": null,
    "source": "api",
    "sim": null,
    "deviceId": null,
    "clientRef": "order-1043",
    "createdAt": "2026-10-23T12:00:00.000Z",
    "updatedAt": "2026-10-23T12:00:00.000Z"
  }
}

Авторизация и лимиты

Ключ передаётся в заголовке Authorization: Bearer trk_… (или X-Api-Key: trk_…). Ключей может быть несколько — например, отдельный для каждой системы; ненужный ключ отзывается в кабинете. Храните ключ на сервере: не вставляйте его в код страниц и мобильных приложений.

Ошибки

При ошибке API возвращает HTTP-статус и тело {"error": {"code": "…", "message": "…"}}. Сообщение — по-русски, его можно показать пользователю.

HTTPcodeЧто значит
400invalid_request, invalid_phone, text_too_long, missing_vars, invalid_jsonОшибка в запросе
401unauthorizedНет ключа, ключ неверный или отозван
403account_blockedАккаунт заблокирован
404not_foundНет такого сообщения, вебхука или метода
409limit_reachedПревышено число вебхуков или ключей
422rejectedSMS не принято: исчерпан лимит тарифа, не подключён телефон, превышено число телефонов. Сообщение сохраняется со статусом rejected и возвращается в поле message
429rate_limitedСлишком много запросов
502gateway_errorСервер телефонов не принял сообщение — можно повторить запрос с тем же clientRef

Отправить SMS

POST/messages

ПолеТипОписание
phonestringОбязательно. Номер получателя: +79001234567; 89001234567 и 9001234567 приводятся к +7
textstringОбязательно. Текст, до 1600 символов (длинные SMS телефон отправит частями)
simintegerSIM-карта: 1, 2 или 3. По умолчанию — основная SIM телефона
deviceIdstringТелефон из GET /devices. По умолчанию — любой подключённый
templatestringВместо text: id или название шаблона из кабинета (раздел «Шаблоны»)
varsobjectЗначения подстановок: для текста «Здравствуйте, {имя}!» — {"имя": "Анна"}. Регистр не важен. Не хватает значения — ошибка 400 missing_vars
sendAtstringОтправить позже: дата и время ISO 8601 с часовым поясом, например 2026-11-01T10:00:00+03:00, не дальше 90 дней. Сообщение получает статус scheduled; лимит тарифа и выбор телефона проверяются в момент отправки
shortLinksbooleanЗаменить длинные ссылки в тексте на короткие smstrubka.ru/s/… со счётчиком переходов (отчёт — в кабинете, раздел «Сообщения»). По умолчанию — как включено в кабинете; false оставит ссылки как есть
clientRefstringВаш идентификатор (номер заказа, id записи в CRM), до 100 символов. Повторный запрос с тем же clientRef не отправит SMS второй раз — вернётся то же сообщение с кодом 200 и заголовком Idempotent-Replayed: true. То же самое — заголовок Idempotency-Key
POST /api/v1/messages
{ "phone": "+79001234567", "template": "Напоминание о записи", "vars": { "имя": "Анна", "время": "11:30" },
  "sendAt": "2026-10-25T09:00:00+03:00", "clientRef": "visit-5512" }

DELETE/messages/{id} — отменить запланированное сообщение (статус станет cancelled). Отправленные отменить нельзя.

GET/templates — шаблоны аккаунта: {"templates": [{"id", "name", "text", "vars": ["имя", "время"]}]}.

Ответы: 202 — принято; 200 — повтор по clientRef; 422 — не принято (см. ошибки).

Статусы сообщений

statusОписание
scheduledЗапланировано на sendAt
cancelledЗапланированное сообщение отменено
queuedПередано телефону, ждёт отправки (телефон соблюдает паузу и лимит из настроек)
sentТелефон отправил SMS оператору
deliveredОператор подтвердил доставку получателю
failedНе отправлено или не доставлено; причина — в поле error
rejectedНе принято «Трубкой» (лимит тарифа, нет телефона)
receivedВходящее SMS получено
forwardedВходящее SMS передано в Битрикс24

Статусы идут только вперёд: queued → sent → delivered или failed. Не все операторы присылают отчёт о доставке — тогда сообщение остаётся в статусе sent. Если телефон был выключен больше суток, сообщение не отправляется.

Журнал и статус сообщения

GET/messages/{id} — одно сообщение: {"message": {…}}.

GET/messages — журнал, сначала новые. Параметры: direction (out | in), status, phone, since (ISO 8601), before, limit (1–100, по умолчанию 50).

GET /api/v1/messages?direction=in&since=2026-10-01T00:00:00Z&limit=100

{ "messages": [ … ], "next": "2026-10-20T08:15:00.000Z" }

Если next не null, следующая страница — тот же запрос с before=<next>. Журнал хранится 12 месяцев.

Телефоны и аккаунт

GET/devices — подключённые телефоны:

{ "devices": [ { "id": "Zx8kP…", "name": "Samsung Galaxy A54", "online": true, "lastSeen": "2026-10-23T11:58:00Z",
                 "sims": [ { "sim": 1, "operator": "МТС", "phone": null }, { "sim": 2, "operator": "Билайн", "phone": null } ] } ] }

GET/account — тариф и использование:

{ "email": "info@example.ru", "name": "ООО «Ромашка»",
  "plan": { "code": "month", "name": "Безлимит на месяц", "monthlyLimit": null, "devices": 3, "paidUntil": "2026-11-16T09:00:00Z" },
  "usage": { "month": 1250, "remaining": null }, "phoneConnected": true }

Вебхуки

Вебхук — адрес вашей системы, куда «Трубка» отправляет события запросом POST с JSON. Добавляется в кабинете (раздел «API») или через API:

GET/webhooks · POST/webhooks · DELETE/webhooks/{id} · POST/webhooks/{id}/test

POST /api/v1/webhooks
{ "url": "https://crm.example.ru/trubka/webhook", "events": ["message.received", "message.status"] }

201 Created
{ "webhook": { "id": "12", "url": "https://crm.example.ru/trubka/webhook", "events": [ … ], "active": true,
               "secret": "whsec_…" } }

Секрет нужен для проверки подписи; через API он возвращается только при создании, в кабинете — всегда. Адрес — только https:// и доступный из интернета. До 5 вебхуков на аккаунт.

События

eventКогда
message.receivedКлиент прислал SMS на номер подключённого телефона
message.statusСтатус исходящего изменился: sent, delivered, failed, rejected — для SMS из API, Битрикс24 и кабинета
webhook.testПроверка из кабинета или POST /webhooks/{id}/test
POST https://crm.example.ru/trubka/webhook
Content-Type: application/json
X-Trubka-Event: message.received
X-Trubka-Delivery: 3f1e0c2a-…
X-Trubka-Signature: t=1761220800,v1=5d2c…

{
  "id": "3f1e0c2a-…",
  "event": "message.received",
  "createdAt": "2026-10-23T12:00:00.000Z",
  "data": {
    "message": { "id": "…", "direction": "in", "phone": "+79001234567", "text": "Когда доставка?",
                 "status": "received", "sim": 1, "deviceId": "Zx8kP…", "createdAt": "…", … }
  }
}

Ответьте кодом 2xx в течение 10 секунд. Иначе «Трубка» повторит запрос через 1, 5, 15 минут, 1, 3, 6, 12 и 24 часа. Одно событие может прийти дважды (например, если ответ потерялся в сети) — сверяйте id события. Журнал доставок — в кабинете, раздел «API».

Проверка подписи

Заголовок X-Trubka-Signature: t=<unix-время>,v1=<подпись>, где подпись — HMAC-SHA256 в hex от строки <t>.<тело запроса> на секрете вебхука. Проверяйте по исходному телу (до разбора JSON) и отклоняйте запросы старше 5 минут.

PHP

$body = file_get_contents('php://input');
preg_match('/t=(\d+),v1=([0-9a-f]+)/', $_SERVER['HTTP_X_TRUBKA_SIGNATURE'] ?? '', $m);
$ok = $m && abs(time() - (int)$m[1]) < 300
   && hash_equals(hash_hmac('sha256', $m[1] . '.' . $body, $secret), $m[2]);
if (!$ok) { http_response_code(401); exit; }
$event = json_decode($body, true);

Python

import hmac, hashlib, re, time

def verify(body: bytes, header: str, secret: str) -> bool:
    m = re.match(r"t=(\d+),v1=([0-9a-f]+)", header or "")
    if not m or abs(time.time() - int(m[1])) > 300:
        return False
    expected = hmac.new(secret.encode(), m[1].encode() + b"." + body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, m[2])

Node.js

import crypto from 'node:crypto';

export function verify(rawBody, header, secret) {
  const m = /t=(\d+),v1=([0-9a-f]+)/.exec(header || '');
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}

Советы

Поддержка разработчиков: help@smstrubka.ru — пришлите id сообщения или события, и мы найдём его в журнале.