DEVELOPER DOCS

Документация для провайдеров

Как SMProxy App работает с подписками: deep-links, заголовки ответа, Provider ID, сообщения, GeoIP/GeoSite и кабинет провайдера. Справочник отражает поведение приложения на текущий момент.

Сейчас всё бесплатно — на всё время открытого тестирования
01

Обзор

SMProxy App — это бесплатный кроссплатформенный клиент для Vless/Reality, VMess, Trojan, Shadowsocks, Socks, WireGuard и AmneziaWG на ядре Xray. Приложение не поставляет серверы и не предоставляет VPN-сервис — пользователь импортирует подписку (конфигурацию) от провайдера и подключается. Доступно на iOS, iPadOS, macOS, Apple TV, Android, Android TV, Windows и Linux.

Это универсальный клиент: он импортирует конфигурацию и подключается. Всё остальное — брендинг, маршрутизация, срок действия, статистика — определяется тем, что провайдер присылает в ответе на запрос подписки и настройками аккаунта провайдера.

📘
Читать эти доки через ИИ-ассистента

Документация опубликована в машинно-читаемом виде: Claude Code, Cursor, Codex и другие MCP-клиенты читают настоящие форматы заголовков и ссылок, а не выдумывают их.

claude mcp add --transport http smproxy-docs https://gitmcp.io/SMProxy/docs
Настройка для других клиентов →
↑ К оглавлению
02

Добавление подписки

Три способа импорта:

🔗
Ссылка на подписку
Вставьте ссылку https://… — сервер возвращает тело конфигурации и заголовки.
📷
QR-код
Отсканируйте QR, кодирующий URL или deep-link smproxy://.
Deep-link
Ссылка smproxy://…, открытая из браузера/сообщения или вставленная в форму добавления — разрешается на всех платформах.

Тело подписки может быть списком ссылок vless://…, base64-блобом или JSON-конфигами. Вся маршрутизация, брендинг и срок действия приходят в заголовках ответа (раздел 04).

↑ К оглавлению
03a

Заголовки запроса (клиент → сервер)

Каждый запрос подписки несёт эти заголовки, чтобы панель могла опознать устройство и привязать к нему подписку (скачивание профиля маршрутизации несёт только User-Agent, скачивание гео-баз — ничего):

Заголовок
Значение
X-Hwid
Стабильный id устройства. Считается один раз при первом запуске и больше не меняется — ни при обновлении приложения, ни при переустановке. Показан на экране «О приложении»; с ним сверяются ссылки addhw. У каждого клиентского приложения своя схема HWID, поэтому устройство, привязанное в другом клиенте, для панели будет другим HWID.
X-Device-Os
Android · iOS · macOS · tvOS · Windows · Linux
X-Ver-Os
Версия ОС — например 16, 18.6, 10.0.26200
X-Device-Model
Модель устройства — например SM-S942B, iPhone16,2, PC
X-Device-Locale
ru_RU и т.п. — язык и регион устройства
X-App-Version
2.0.5 (422) — маркетинговая версия + билд одной строкой
X-App-Build
422 — номер билда отдельно — по нему и ориентироваться (Android 423+, Apple 313+, desktop 199+; старые сборки шлют только X-App-Version и User-Agent)
User-Agent
SMProxy/<версия>/<билд>-<ОС> — например SMProxy/2.0.5/422-Android. Используйте, когда разным сборкам нужно отдавать разный контент

Имена заголовков регистронезависимы.

User-Agent — один формат на всех платформах

SMProxy/<маркетинговая версия>/<билд>-<ОС>
Платформа
Пример
Android
SMProxy/2.0.5/422-Android
iOS / iPadOS
SMProxy/2.0.9/312-iOS
macOS
SMProxy/2.0.9/312-macOS
tvOS
SMProxy/2.0.9/312-tvOS
Windows
SMProxy/2.0.2/198-Windows
Linux
SMProxy/2.0.2/198-Linux

Отправляется при каждом запросе подписки и при скачивании профиля маршрутизации по URL. Ориентироваться нужно на номер билда — третий сегмент растёт с каждым релизом и никогда не повторяется, а маркетинговая версия может не меняться несколько билдов подряд. Панель, которая отдаёт разным сборкам разный контент, разбирает его так:

^SMProxy/[^/]+/(\d+)-(Android|iOS|macOS|tvOS|Windows|Linux)$
↑ К оглавлению
04

Метаданные подписки

Ответ эндпоинта подписки — это тело (список серверов) плюс метаданные (брендинг, срок действия, обновления). Метаданные можно передать двумя способами, и клиент читает оба.

AHTTP-заголовок ответа
Profile-Title: My VPN
BСтрока-комментарий в теле
#profile-title: My VPN
Приоритет — у HTTP-заголовка. Строка #key: value в теле используется, только если одноимённого заголовка нет. Это позволяет отдавать метаданные тем, кто не может задавать заголовки (статический хостинг, файлы в Telegram и т.п.).
Значения вкл/выкл. Любой заголовок, который включает или выключает функцию, принимает набор эквивалентов без учёта регистра: 1 / on / true / yes означают включено, а 0 / off / false / noвыключено.
Функции обхода: ваша настройка важнее пользовательской. Для всех четырёх заголовков обхода — s-fragment, s-dns, s-noise, s-resolve — присланное вами значение важнее собственного переключателя пользователя, включая off. Переключатель пользователя работает только тогда, когда вы не прислали ничего. Так сделано намеренно: эти параметры обязаны совпадать с тем, что ожидает ваш сервер, и самостоятельное включение пользователем просто разорвало бы соединение.

Живые обновления подписки

Провайдер может менять настройки обхода и маршрутизацию подписки, не дожидаясь следующего перезапроса подписки: изменения, сделанные в кабинете провайдера, доходят до подключённых устройств за считаные минуты и применяются без переподключения — новые значения вступают в силу при следующем подключении. Действуют те же правила, что и для HTTP-заголовков выше, — в частности, они учитываются только пока провайдер активен. Механизм доставки внутренний для приложения: на стороне эндпоинта подписки ничего реализовывать не нужно.

Синтаксис строки в теле

  • Начинается с # , затем ключ [A-Za-z0-9-]+ , затем значение.
  • Двоеточие необязательно — #profile-title My VPN тоже сработает.
  • Ключ регистронезависим.
  • Первое вхождение ключа побеждает, повторы игнорируются.
  • Тело сканируется и как есть, и после полного base64-декодирования — работает и для текста, и для одного base64-блоба (обычный формат vless-ссылок).
  • Из тела читаются только ключи из таблицы ниже; строки с другими ключами игнорируются.
base64 для не-ASCII (например, кириллицы)

Текстовые поля можно закодировать, добавив префикс base64: — работает и в заголовке, и в теле. Применимо к profile-title, s-title, announce, sub-expire-button-text.

#profile-title: base64:0JzQvtC5IFZQTg== → «Мой VPN»

Ключи

Каждый ключ работает и как заголовок Ключ: значение, и как строка тела #ключ: значение. Все — бесплатно.

Брендинг и ссылки

Ключ (заголовок / #тело)
Назначение
profile-title / s-title🔒
Заголовок подписки (текст или base64:)
s-sitename🔒
Название провайдера/сайта
s-siteurl🔒
URL сайта провайдера
s-tgbot / x-tgbot🔒
Ссылка на Telegram-бот/канал
support-url🔒
Ссылка на поддержку
announce🔒
Текст баннера-объявления (текст или base64:), до 5 строк — длиннее обрезается многоточием
announce-url🔒
Делает объявление кликабельным — открывает этот URL
support-email🔒
Email поддержки — добавляет на карточку подписки кнопку «Написать в поддержку»
profile-web-page-url🔒
Веб-страница провайдера

Статус подписки и продление

Ключ (заголовок / #тело)
Назначение
subscription-userinfo🔒
upload=…; download=…; total=…; expire=… (квота + epoch окончания)
sub-expire / s-showexpire🔒
Показывать состояние срока действия
sub-expire-button-text🔒
Подпись кнопки продления (текст или base64:)
sub-expire-button-link-site🔒
Кнопка продления → сайт
sub-expire-button-link-tg🔒
Кнопка продления → Telegram
notification-subs-expire🔒
1 — слать уведомления «подписка истекает через N дней», начиная за 3 дня до окончания. Независим от sub-expire / s-showexpire. Отсутствует = без уведомлений. Также как #notification-subs-expire: 1 в теле.

Обновление и данные

Ключ (заголовок / #тело)
Назначение
profile-update-interval🔒
Интервал авто-обновления подписки (часы)
fallback-url🔒
Резервный URL подписки
geoipurl / geositeurl🔒
Источники баз GeoIP / GeoSite (раздел 07)
sort-order🔒
Порядок серверов в списке: ping (сначала быстрые, по замеренной задержке — без замера уходят в конец), name (по алфавиту) или none (как пришли, по умолчанию). Неизвестные значения считаются как none.

Миграция и доступ

Ключ (заголовок / #тело)
Назначение
new-url🔒
Полностью заменить URL подписки (миграция) — см. ниже
new-domain🔒
Заменить только хост URL: host или host:port (безопасно для персональных ссылок). Можно задать из кабинета провайдера, не трогая панель — см. раздел 05b
fallback-domains🔒
Запасные хосты через запятую. Если хост подписки перестал отвечать, приложение пробует их по порядку, сохраняя путь и токен каждого пользователя. В отличие от new-domain ничего не меняется навсегда — это выход, когда основной адрес заблокирован, а не переезд. Задаётся из кабинета провайдера или отправляется вами (раздел 05b)
hide-settings🔒
Скрывает от пользователя URL подписки и её конфиг — подробности в разделе 04b. 1 скрывает, 0 показывает; отсутствие заголовка ничего не меняет.

Идентификация

Ключ (заголовок / #тело)
Назначение
providerid / s-providerid🔒
Provider ID (раздел 05). s- вариант предпочтителен; также читается из URL ?providerid=
subid🔒
Стабильный идентификатор подписки — позволяет менять URL подписки без появления дубликатов у пользователей (раздел 05a)

Обход блокировок

Ключ (заголовок / #тело)
Назначение
s-noise🔒
Шумовые пакеты перед хендшейком — мусорный трафик, отправляемый до соединения, чтобы DPI не распознал его начало. Работает вместе с s-fragment (общий выход). on / 1 — параметры по умолчанию (rand, пакет 50-100, задержка 10-20); off / 0 — выключить. Полная форма: type=rand;packet=50-100;delay=10-20 или позиционно rand,50-100,10-20; type — rand, str или hex. → подробно в разделе 08
s-resolve🔒
Резолв адреса сервера через DoH до подъёма туннеля — для сетей, где локальный DNS подменяет ответ для домена вашей ноды. on / 1, off / 0 или URL резолвера с необязательным bootstrap-IP. → подробно в разделе 08
s-fragment🔒
Фрагментация TLS ClientHello против DPI по SNI (полный справочник — раздел 08).
s-dns🔒
DNS-over-HTTPS внутри туннеля: on / 1 — встроенный резолвер по умолчанию (dnsforge.de); URL — этот резолвер; off / 0 — выключить. → подробно в разделе 08
🔒 Ключи с замком работают только при активном провайдереполный список.

Баннер провайдера — banner-*

Баннер на карточке подписки, показывается только при активном provider id (совместимо с INCY). Цвета — только #RRGGBB; всё остальное игнорируется, и берётся цвет темы приложения. Цвет надписи на кнопке подбирается автоматически по яркости кнопки, поэтому остаётся читаемым и на светлом, и на тёмном фоне.

Заголовок
Значение и назначение
banner-text🔒
текст или base64: — текст баннера, до 5 строк
banner-button-text🔒
текст или base64:, до 25 символов — надпись на кнопке
banner-button-url🔒
URL или deep-link — куда ведёт кнопка
banner-bg-color🔒
#RRGGBB — фон баннера
banner-button-color🔒
#RRGGBB — фон кнопки

Параметры обхода — сводка

Сводка по четырём заголовкам обхода; подробный разбор каждого — в разделе 08.

Заголовок
Значения
s-fragment
on | off | packets=<tlshello|1-3|all>;length=<min-max>;interval=<min-max>[;maxsplit=<n>] · по умолчанию, если прислано только on: packets=tlshello;length=50-100;interval=10-20
s-noise (s-noises)
on | off | type=<rand|str|hex>;packet=<value>;delay=<min-max> · по умолчанию, если прислано только on: type=rand;packet=50-100;delay=10-20
s-resolve
on | off | <doh-url>[;ip=<bootstrap>][, …] · по умолчанию, если прислано только on: встроенный резолвер
s-dns
on | off | <doh-url> · по умолчанию, если прислано только on: встроенный резолвер (dnsforge.de)

Совместимость с именами заголовков INCY / Happ

Если вы уже настраиваете другой клиент, приложение понимает и его отдельные ключи и сворачивает их в эквивалентную настройку. Наш заголовок s-* всегда важнее — ключи ниже читаются только когда соответствующего s-* нет. *-enable: 0 — явное выключение.

Их ключи
Соответствует
fragmentation-enable · fragmentation-packets · fragmentation-length · fragmentation-interval · fragmentation-maxsplit
s-fragment
noises-enable · noises-type / noises-packet-type · noises-packet · noises-delay · noises-rand
s-noise
server-address-resolve-enable · server-address-resolve-dns-domain · server-address-resolve-dns-ip
s-resolve

noises-rand: <n> превращается в type=rand;packet=<n>. noises-rand-range не поддерживается.

Откуда может прийти значение и что важнее: 1) HTTP-заголовок ответа — высший приоритет; 2) строка #ключ: значение в теле (а для команд маршрутизации — строка *://routing/… без префикса) — используется, если заголовка нет; 3) собственная настройка приложения — если провайдер ничего не присылает. Для четырёх заголовков обхода значение провайдера важнее переключателя пользователя, включая off.

Текстовые значения принимаются как обычный UTF-8 или с префиксом base64:; любое значение заголовка может прийти и зашифрованным как crypt1/.

Миграция URL: new-url / new-domain

Перенацеливают сохранённый URL подписки — провайдер может переехать, а пользователю не нужно добавлять подписку заново.

  • new-url — заменяет URL целиком (любой формат), один и тот же для всех подписок.
  • new-domain — меняет только хост (схема, порт, путь, query сохраняются); каждая подписка хранит свой путь/токен — безопасно для персональных URL. Если присланы оба — побеждает new-url.

Применяется, только если в том же ответе есть providerid И у этого провайдера provider_active == true. Мигрируются все локальные подписки с этим providerId. Флаг locked сохраняется: подписка из crypt1/addhw остаётся заблокированной на новом URL.

Алиасы для совместимости (Happ / INCY)

Заголовки, которые используют другие клиенты, читаются как их аналоги в SMProxy, когда заголовка SMProxy нет — панель, написанная под Happ или INCY, работает без изменений. Алиасы принимаются и в HTTP-заголовках, и в теле подписки в форме #key: value.

Шлёт другой клиент
Читается как
subscription-name
profile-title — запасное имя INCY
content-disposition: attachment; filename="x.txt"
profile-title — имя файла без .txt / .yaml / .yml; самый низкий приоритет
homepage
profile-web-page-url — INCY
hide-url
hide-settings 1/true скрывает, 0/false открывает
sub-info-text
banner-text — Happ «расширенные объявления»; 0 = баннера нет
sub-info-button-text / sub-info-button-link
banner-button-text / banner-button-url
sub-info-color (red · blue · green)
banner-bg-color — переводится в hex-цвет
sub-expire-button-link
sub-expire-button-link-tg для t.me / tg://, иначе sub-expire-button-link-site — одиночная форма Happ
routing-enable: 0
routing: routing/off — Happ «выключить маршрутизацию»
subscription-userinfo: 0
заголовка нет — INCY «скрыть блок трафика»
expire=<миллисекунды> в subscription-userinfo
секунды — значения больше 32 000 000 000 считаются миллисекундами

При наличии обоих заголовков побеждает заголовок SMProxy.

↑ К оглавлению
04a

Описание сервера

Подпись для конкретного сервера, которая показывается под его названием вместо стандартной метки протокола (VLESS | TCP | Reality). Пригодится, чтобы сказать то, чего метка не скажет — Netflix / стриминг, для игр, 10 Гбит/с. В отличие от ключей выше передаётся не на всю подписку, а вместе с каждым отдельным сервером.

🔒 Работает только при активном Provider ID — так же, как s-fragment и s-dns. Без активного Provider ID приложение показывает метку протокола.
Правило
Значение
serverDescription
Название поля
base64
Кодировка в ссылках (в JSON-конфигах — обычный текст)

В share-ссылках

Внутри #fragment, после названия, через ?:

vless://uuid@host:443?security=reality&type=tcp#🇳🇱 Нидерланды?serverDescription=0J3QsNC00ZHQttC90YvQuQ==

Тот же синтаксис работает для vless://, trojan://, ss:// и socks://.

В vmess-ссылках

Поле внутри base64-закодированного JSON:

{ "ps": "🇳🇱 Нидерланды", "add": "host", "port": "443", "serverDescription": "0J3QsNC00ZHQttC90YvQuQ==" }

В JSON-конфигах

Внутри объекта meta рядом с remarks — здесь значение указывается обычным текстом, без base64:

{ "remarks": "🇳🇱 Нидерланды", "meta": { "serverDescription": "Netflix / стриминг" }, "outbounds": [ … ] }

В ссылках значение, которое не является корректным base64 или декодируется в пустоту, игнорируется — сервер сохраняет метку протокола, а не показывает сломанный текст.

↑ К оглавлению
04b

Скрытие URL подписки — hide-settings

hide-settings: 1 не даёт URL подписки и её содержимому покинуть устройство. Подписка переходит ровно в то же состояние, что зашифрованная (crypt1) или привязанная к устройству (addhw): внутри приложение считает «заблокирована» и «скрыта провайдером» одним и тем же условием.

Что блокируется

Блокируется
Подробности
Поле URL в редакторе подписки
Поле вообще не отображается — пользователь не может прочитать, скопировать или изменить адрес
«Скопировать ссылку» в меню подписки
Убирается
«Показать конфиг»
Убирается, и сама процедура экспорта конфига отказывается что-либо возвращать, даже если её вызвать из другого места приложения
Изменение URL при обновлении
Правка, меняющая адрес, игнорируется; имя и профиль маршрутизации остаются редактируемыми

Что продолжает работать

Подключение, пинг, статистика трафика, обновление подписки, список серверов, выбор сервера, профили маршрутизации, интерфейс окончания подписки — пользователь просто не видит адрес и не может его выгрузить.

Значения и поведение

Значение
Действие
1
Скрыть. Также сбрасывает незакрытый запрос пользователя на разблокировку.
0
Показать — и одновременно снять блокировку crypt1 с этой подписки, если провайдер её ставил. Так задумано: ссылка принадлежит провайдеру, поэтому явный 0 от активного провайдера открывает подписку полностью.
отсутствует
Ничего не меняет — сохраняется текущее состояние. Флаг залипающий: выставили один раз — держится при всех обновлениях, пока не пришлёте явный 0.

Любое значение, кроме 1 и 0, игнорируется и пишется в лог как «значение не распознано» — on / off / true / false здесь не принимаются.

Принимается как HTTP-заголовок ответа, как #hide-settings: 1 в теле и из кабинета провайдера. Работает только при активном provider id — неактивный или неизвестный провайдер не может ни скрыть, ни раскрыть ничего.

↑ К оглавлению
04c

Два канала, одно правило: кто задал значение, тот его и снимает

Одни и те же ключи могут приходить двумя путями — в ответе подписки (HTTP-заголовок или строка #ключ:) и из кабинета провайдера (доходит до подключённых устройств за считаные минуты). Работает одно правило:

Ситуация
Результат
Ключ есть в ответе подписки
Побеждает его значение, поле помечается «задано подпиской»
Ключ есть в кабинете провайдера
Его значение важнее значения из подписки, поле помечается «задано кабинетом»
Ключа нет в обновлении из кабинета, а поле было задано кабинетом
Поле очищается — вы удалили его в кабинете, значит оно пропадает и в приложении
Ключа нет в обновлении из кабинета, а поле было задано подпиской
Поле сохраняется — кабинет никогда не стирает то, что задала подписка

Коротко: кто записал последним — тот и прав, а убрать значение может только та сторона, которая его поставила. Именно так снимается объявление или баннер — очистите его в кабинете, и он исчезнет при следующем обновлении; ответ подписки продолжает отдавать то, что в нём есть.

Поля без «пустого» состояния — название подписки, брендовые ссылки, hide-settings — автоматически не очищаются: чтобы изменить их, пришлите новое значение.

⚠️ Правило действует для всех ключей, включая обход (s-fragment, s-noise, s-resolve, s-dns) и маршрутизацию: если вы задали s-fragment в кабинете, а потом убрали его там, фрагментация выключится при следующем обновлении.

↑ К оглавлению
05

Provider ID

Provider ID связывает подписку с аккаунтом провайдера, чтобы провайдер видел статистику использования своей подписки. Он ничего не ограничивает — приложение полностью работает и без него. Бесплатно.

Как передать (любой способ)
1Query в URL — …?providerid=<ProviderID>
2Комментарий в теле подписки — строка #providerid <ProviderID>
3HTTP-заголовок ответа — providerid: или s-providerid:

Если указано несколько — приоритет у HTTP-заголовка. Свой Provider ID (и возможность задать свой произвольный) вы получаете в кабинете провайдера — см. раздел 08.

Без Provider ID подписка работает полностью, провайдер просто не получает статистику. Что действительно зависит от активного Provider ID — в таблицах ниже; те же таблицы повторены в конце страницы для быстрой сверки.

🔒Требует активного Provider ID

Область
Ключи
Обход блокировок
s-fragment s-noise s-noises s-resolve s-dns — и их аналоги из INCY/Happ fragmentation-* noises-* server-address-resolve-*
Переезд и запасные адреса
new-url new-domain fallback-url fallback-domains
Брендинг провайдера
s-siteurl s-sitename s-tgbot x-tgbot sort-order serverDescription
Баннер и контакты
banner-text banner-button-text banner-button-url banner-bg-color banner-button-color announce-url support-email
Интерфейс окончания подписки
sub-expire s-showexpire sub-expire-button-text sub-expire-button-link-site sub-expire-button-link-tg notification-subs-expire
Доступ
hide-settings

Работает и без Provider ID

Ключ
Примечание
profile-title / s-title
Название подписки
profile-update-interval
Интервал автообновления
profile-web-page-url
Веб-страница провайдера
announce
Сам текст объявления — но announce-url (кликабельность) требует активного провайдера
support-url
Ссылка на поддержку
subscription-userinfo
Квота и дата окончания
subid
Стабильный id подписки
routing autorouting routing-update-url routing-update-interval routing-profile
Профили маршрутизации, включая формы deep-link-команд — профиль несёт только правила, активный провайдер не нужен
geoipurl / geositeurl
Адреса гео-баз — только в ответе подписки

Гейт работает одинаково независимо от того, как пришло значение: HTTP-заголовок, строка #ключ: в теле или кабинет провайдера.

↑ К оглавлению
05a

Подтверждение домена — статистика без Provider ID

Статистика работает только тогда, когда приложение знает ваш Provider ID. Если сборка его не отправляет — старый релиз, клиент, который вы не контролируете, конфиг, добавленный вручную — такие check-in никуда не попадают, и вы теряете из виду собственных пользователей.

Подтвердите домен, с которого раздаются ваши подписки, и check-in без Provider ID будут автоматически засчитываться вам — по URL подписки, который сообщает приложение.

Как подтвердить
1Откройте provider.smproxy.io/provider/domains и добавьте домен. Можно вставить полный URL подписки — домен будет извлечён автоматически.
2Добавьте TXT-запись, показанную на странице.
3Нажмите Проверить. DNS обновляется не мгновенно — если с первого раза не вышло, подождите пару минут и попробуйте снова.
Name: smproxy-verification.your-domain.com Value: 8f3a91c24b774c1e9a025e6d1f0b7c33
☁️ Быстрый путь через Cloudflare. Если DNS у вас на Cloudflare, нажмите Добавить через Cloudflare. Вы попадёте на экран согласия Cloudflare, мы сами создадим TXT-запись, и домен будет подтверждён сразу — копировать ничего не нужно. Мы запрашиваем доступ только к DNS, запись остаётся на месте после проверки.

Что покрывается

Поддомены включены. Подтверждение example.com покрывает sub.example.com, eu.sub.example.com и любую другую глубину — добавлять их отдельно не нужно.

Сопоставление идёт по регистрируемому домену, поэтому многоуровневые суффиксы обрабатываются корректно: example.co.uk — домен, которым можно владеть, co.uk — нет, а evil-example.com никогда не совпадёт с example.com.

Домен принадлежит одному провайдеру. Если его уже подтвердил кто-то другой, повторное добавление отклоняется.

Приоритет — Provider ID важнее

В check-in пришло
Кому засчитывается
Существующий Provider ID
Этому провайдеру — домен не учитывается
Provider ID нет, URL подписки на подтверждённом домене
Владельцу домена
Неизвестный нам Provider ID, URL на подтверждённом домене
Владельцу домена
Ни того, ни другого
Никому; check-in помечается как неатрибутированный

Подтверждение домена никогда не перебивает явный Provider ID — оно только закрывает пробел, когда его нет или он непригоден.

Где виден домен

  • Статистика — разбивка Домены подписок: сколько устройств использует каждый хост, и хост под subid каждого устройства. Хосты показываются полностью, поэтому sub.example.com отличим от sub3.example.com.
  • Сообщения — можно отправить сообщение на домен: *.example.com — домен и все его поддомены, либо отдельный хост — только его пользователям.

Показывается только хост — токены подписки из URL не отображаются никогда.

↑ К оглавлению
05b

Перевод пользователей на другой домен

Домен подписок заблокировали — или вы просто переезжаете на новый. Оба случая решаются из кабинета: в панели менять ничего не нужно, пользователям заново добавлять подписку тоже.

Оба правила привязаны к домену, на который пришёл запрос, поэтому их можно держать несколько одновременно — по одному на поддомен — и удалять по отдельности.

Переезд — new-domain

Откройте provider.smproxy.io/provider/domains, нажмите Перенести рядом с подтверждённым доменом и выберите:

  • Откуда*.example.com, чтобы перенести подписки со всего домена вместе с поддоменами, или отдельный хост вида sub.example.com, чтобы перенести только его.
  • Куда — новый хост. Он должен быть подтверждён в том же аккаунте, чтобы никто не мог направить ваших пользователей на домен, которым не владеет.

После этого каждый запрос подписки, пришедший на старый хост, получает заголовок new-domain, и приложение само перезаписывает адрес. У каждого пользователя сохраняются его путь и токен:

old.example.com/sub/abc123 → new.example.com/sub/abc123
Цепочки запрещены. Если у b.example.com уже есть свой переезд, сделать его получателем другого нельзя — пользователи получили бы адрес, который тут же переезжает снова.

Запасные домены — fallback-domains

Нажмите Запасные рядом с доменом и перечислите хосты для отката, по одному в строке. Каждый должен быть вашим подтверждённым доменом. Список отправляется как fallback-domains для запросов на этот домен, и приложение перебирает его по порядку, если основной хост перестал отвечать.

Переезд — когда вы действительно переехали; запасные домены — когда основной адрес может стать недоступным и вы хотите, чтобы приложение само нашло дорогу.

Два канала доставки

Оба правила доходят до приложения двумя независимыми путями:

  • Заголовки ответа подпискиnew-domain / fallback-domains, как описано выше. Требуется, чтобы запрос всё ещё доходил до одного из ваших доменов.
  • Собственный сервисный канал приложения — приложение получает new-domain / fallback-domains, настроенные вами для этого домена, и по своему сервисному соединению, без запроса подписки. Они применяются при следующем обновлении подписки: new-domain перенацеливает сохранённый URL навсегда, запасные хосты сохраняются и используются, когда основной хост перестаёт отвечать.

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

Требуется сборка приложения конца августа 2026 года или новее; более старые сборки работают только через заголовки.

↑ К оглавлению
05c

Идентификатор подписки — subid

Какую проблему решает. Когда вы выдаёте пользователю новый URL подписки — переезд домена, перевыпуск токена, смена панели — приложение не может понять, что это та же самая подписка. Оно добавляет вторую запись, и у пользователя оказывается две копии одного и того же.

Передайте subid — и это прекратится: при совпадении subid приложение считает подписку той же самой, заменяет её URL на новый и обновляет серверы на месте.

Откуда может прийти (любой способ — правила те же, что у остальных метаданных)
1HTTP-заголовок — subid: my-sub-42
2Комментарий в теле — строка #subid: my-sub-42 в теле подписки
3Query в URL — …?subid=my-sub-42

HTTP-заголовок побеждает комментарий в теле, а тот — значение из URL. Формат: буквы, цифры, -, _ и пробелы ([A-Za-z0-9 _-]+). Всё остальное игнорируется — значение просто не сохраняется.

Ситуация
Что делает приложение
URL уже добавлен
Обновляет эту подписку (поведение не меняется)
Новый URL, subid совпадает с существующей подпиской
Обновляет именно её: новый URL и свежие серверы, пользовательское имя сохраняется
Новый URL, subid новый или отсутствует
Добавляет новую подписку (поведение не меняется)

subid необязателен. Без него всё работает ровно как раньше — дубликаты определяются только по URL. Передавать его стоит просто чтобы сохранять контроль над записями пользователей при смене адресов.

Выбирайте значение, которое никогда не меняется для подписки конкретного пользователя. Секретным оно быть не обязано — оно лишь сравнивается с тем, что уже есть у приложения, — но должно быть стабильным и уникальным для каждой подписки. UUID или ваш внутренний id подписки одинаково подойдут.

Пример
subid: 8f3a91c2-4b77-4c1e-9a02-5e6d1f0b7c33

Доступно на всех платформах: iOS, Android, macOS, Windows, Linux и Apple TV.

↑ К оглавлению
06

Сообщения и уведомления

эксклюзив SMProxy

Провайдер может отправлять пользователям внутренние сообщения. Они приходят внутри ответа проверки Provider ID и попадают во входящие приложения (иконка колокольчика + счётчик непрочитанного), при желании вызывая системное уведомление.

Каждое сообщение: id, title, body, опционально date, type (info/update/promo/warning), url, notify и targeting. Статус прочтения хранится локально на каждом устройстве.

Кому отправлять

Сообщения составляются в кабинете провайдера. По умолчанию сообщение уходит всем; каждый фильтр ниже сужает выборку, и они складываются.

Платформа и сборка приложения

Отметьте платформы, которые должны получить сообщение. Оставьте поле сборки пустым, чтобы охватить все версии этой платформы, или задайте условие (<, , =, , >), чтобы охватить только часть.

Номера сборок свои у каждой платформы — iOS 200 и Android 200 никак не связаны — поэтому условие всегда привязано к платформе. Не отмечено ничего — значит «все платформы»; как только отмечена одна, остальные не получают ничего. Так и добираются до пользователей на старой сборке iOS, не трогая Android:

Отмечено
Условие сборки · Кто получит
ничего
— · все
iOS
— · все пользователи iOS
iOS
< 200 · пользователи iOS ниже сборки 200
iOS, Android
iOS < 200, Android < 300 · старые сборки на обеих, на остальных — ничего

Домен подписки

Выберите подтверждённый домен, чтобы охватить только тех пользователей, чья подписка раздаётся с него — *.example.com для домена и всех его поддоменов или отдельный хост вида sub.example.com только для него. В кабинете видно, сколько устройств сейчас на каждом хосте.

Требуется подтверждённый домен — см. Подтверждение домена (раздел 05a).

Одно устройство

Вставьте HWID, чтобы отправить сообщение ровно одному устройству — удобно, когда вы разбираете проблему с конкретным пользователем.

↑ К оглавлению
07

GeoIP / GeoSite

Geo-правила, которые использует туннель, живут внутри импортированного Xray-конфига и разрешаются по базам geoip.dat / geosite.dat. Приложение поставляется со встроенными базами и может загружать обновлённые из заголовков geoipurl / geositeurl (поддерживается на iOS, Android и настольных ОС); свежезагруженная база заменяет встроенную для движка.

↑ К оглавлению
07a

Профили маршрутизации

Профиль маршрутизации — это полный набор правил: что идёт напрямую, что через туннель и что блокируется, плюс опциональный split-DNS. Это ответ на «у меня подписка — одна ссылка vless://, внутри неё нет маршрутизации»: профиль несёт маршрутизацию отдельно от серверов.

Совместимость формата. Формат профиля тот же, что использует Happ, включая имена полей. Это сделано намеренно: профили ходят между приложениями ссылками и файлами, и совместимость важнее своих названий.

Профиль

{ "Name": "Bypass RU", "GlobalProxy": true, "DirectSites": ["geosite:category-ru", "domain:gosuslugi.ru"], "DirectIp": ["geoip:ru", "192.168.0.0/16"], "ProxySites": ["geosite:youtube"], "BlockSites": ["geosite:category-ads-all"], "RemoteDNSType": "DoH", "RemoteDNSDomain": "https://dnsforge.de/dns-query", "DomesticDNSType": "DoU", "DomesticDNSIP": "77.88.8.8", "DnsHosts": { "example.com": "1.2.3.4" }, "DomainStrategy": "IPIfNonMatch", "UpdateUrl": "https://example.com/routing.json", "UpdateInterval": 24, "LastUpdated": 1756600000 }
Поле
Значение
Name
Имя профиля. Оно же — ключ обновления: импорт профиля с тем же именем заменяет старый, а не создаёт дубликат.
GlobalProxy
Куда идёт трафик, не попавший ни в одно правило: true — через туннель, false — напрямую.
DirectSites / ProxySites / BlockSites
Домены и категории: geosite:ru, domain:example.com, regexp:…
DirectIp / ProxyIp / BlockIp
IP-адреса, подсети и geoip:ru.
RemoteDNS* / DomesticDNS*
Split-DNS: удалённый резолвер отвечает за проксируемые имена, локальный — за прямые. Шесть полей — см. «Поля split-DNS» ниже.
DnsHosts
Статические соответствия домен → IP.
DomainStrategy
Порядок сопоставления правил: AsIs (по умолчанию), IPIfNonMatch, IPOnDemand.
FakeDNS
Принимается для совместимости (true/false, булево или строкой, как у Happ); ядро SMProxy FakeDNS не использует, значение игнорируется.
UpdateUrl
Откуда брать свежую копию профиля. С ним профиль самодостаточен: раздавайте его как base64 или файл, и он всё равно будет обновляться.
UpdateInterval
Как часто обновлять, в часах. 12…168 (неделя); по умолчанию 24.
LastUpdated
Необязательное. Unix-время последнего изменения профиля — число или строка с числом, как в Happ. Показывается в карточке профиля и работает как защита от отката версии — см. ниже.

Правила применяются в порядке block → direct → proxy и ставятся перед правилами, пришедшими с конфигурацией сервера.

GlobalProxy и FakeDNS принимаются и как JSON-булево, и как строки "true" / "false", которые шлют Happ и INCY. Поля, которые другие клиенты добавляют для своего интерфейса — RouteOrder, UseChunkFiles — игнорируются: SMProxy всегда применяет правила в порядке block → direct → proxy.

Поля split-DNS

Поле
Значение
RemoteDNSType
Протокол удалённого резолвера: DoH, DoT, DoQ или DoU (обычный UDP).
RemoteDNSDomain
Адрес резолвера — URL DoH/DoT, например https://dnsforge.de/dns-query. Пусто для обычного UDP.
RemoteDNSIP
IP резолвера. Вместе с RemoteDNSDomain это bootstrap-адрес домена — та же роль, что у ;ip=… в заголовке s-resolve; без домена — обычный UDP-резолвер.
DomesticDNSType / DomesticDNSDomain / DomesticDNSIP
Те же три поля для локального резолвера (прямые имена).
RemoteDns / DomesticDns
Устаревшее написание из старых профилей Happ/INCY — синоним RemoteDNSIP / DomesticDNSIP. Читается только если новое поле отсутствует или пустое. В новых профилях не использовать.

Как используются резолверы:

  • Кто за что отвечает. Remote — имена, идущие через туннель, Domestic — имена, идущие напрямую (DirectSites). Удалённый резолвер опрашивается через туннель, локальный — через физическую сеть.
  • RemoteDNSDomain — ещё и собственный резолвер клиента для служебных запросов (обновление подписки, загрузка профиля), когда подписка не присылает s-resolve. Профиль маршрутизации, таким образом, задаёт DNS для всего, а не только для трафика внутри туннеля.
  • Bootstrap. Суффикс ;ip=… из s-resolve не работает внутри RemoteDNSDomain — там нет разбора, адрес уйдёт с мусором на конце. Bootstrap-адрес кладите в RemoteDNSIP.
  • Цепочка запасных. Сначала резолверы профиля, затем DNS-серверы из самого конфига подключения (их разбиения domains / skipFallback сохраняются). Если все резолверы профиля недоступны, разрешение имён уходит на серверы конфига.
  • Поддержка массивов клиентами: SMProxy начиная с Android 411 / iOS, macOS, tvOS 305 / Windows, Linux 193. Happ, INCY и старые сборки SMProxy читают только строку — присылайте строку, если не уверены, что пользователи обновились.

Строка или массив — расширение SMProxy. RemoteDNSDomain, RemoteDNSIP, DomesticDNSDomain и DomesticDNSIP принимают либо одну строку (классический формат, совместимый с Happ/INCY), либо массив строк:

"RemoteDNSDomain": ["https://dns1.example/dns-query", "https://dns2.example/dns-query"], "RemoteDNSIP": ["1.2.3.4", "5.6.7.8"]

Порядок = приоритет: ядро опрашивает резолверы сверху вниз, вторая запись — запасная для первой. *DNSIP[i] — bootstrap-адрес для *DNSDomain[i]; IP без парного домена работает как обычный резолвер.

Защита от отката — LastUpdated

Когда профиль с тем же Name приходит автоматически (заголовки подписки, обновление по UpdateUrl, ссылка), он заменяет сохранённый только если его LastUpdated больше. Если метки нет с одной из сторон, профиль заменяется безусловно. Увеличивайте значение при каждом изменении профиля. Ручной импорт из интерфейса приложения применяется всегда.

🧩
Не пишите JSON руками
Конструктор соберёт профиль из обычных полей и сразу даст JSON, ссылку smproxy://routing/onadd/… и QR-код. Всё считается в браузере.
Конструктор профиля →

Как передать профиль пользователю

Ссылкой — самый естественный способ: из браузера, из сообщения или по QR-коду.

Ссылка
Что делает
smproxy://routing/add/<base64|url>
Импортировать профиль, не переключаясь на него
smproxy://routing/onadd/<base64|url>
Импортировать и включить
smproxy://autorouting/add/<url> · …/onadd/<url>
То же плюс обновление с этого URL
smproxy://routing/off
Выключить маршрутизацию

Полезную нагрузку можно передать и как ?data=<base64>. URL может вернуть JSON профиля, его base64 или другую ссылку smproxy:// — принимаются все три варианта.

Заголовком подписки — профиль приходит вместе с самой подпиской.

Заголовок
Назначение
routing
Профиль: base64, обычный JSON или URL, откуда его забрать. Обновляется вместе с подпиской.
autorouting
URL, откуда забрать профиль и обновлять его по собственному расписанию.
routing-update-url
Откуда обновлять профиль — если не хочется класть UpdateUrl внутрь самого профиля.
routing-update-interval
Интервал обновления в часах (12…168). Значения вне диапазона подрезаются.
routing-profile
Какой профиль должна использовать эта подписка — по имени.

Профиль, пришедший с подпиской, добавляется в список, но не включается — выбор остаётся за пользователем. Эти ключи работают и без Provider ID.

Заголовки принимают и deep-link-команды. Значением routing / autorouting может быть deep-link-команда — в тех же формах, что кликабельные ссылки, с любым словом схемы (smproxy://, happ://, yourapp://): внутри заголовка слово перед :// не важно.

Значение заголовка
Действие
*://routing/add/<base64|url>
Импортировать профиль в список, не назначать его
*://routing/onadd/<base64|url>
Импортировать и назначить этой подписке
*://autorouting/add/<url> · …/onadd/<url>
То же плюс поддерживать профиль в актуальном состоянии по этому URL
*://routing/off
Отвязать профиль от этой подписки — провайдер выключает её маршрутизацию

Полезная нагрузка может быть зашифрована как crypt1/<base64> — как и любое другое значение заголовка.

Те же команды принимаются и отдельной строкой в теле подписки, прямо среди ссылок на серверы (совместимо с INCY):

vless://uuid@server1:443?security=tls#Server1 vmess://eyJhZGQiOiAic2VydmVyMi... incy://routing/onadd/ewogICJOYW1lIjogIl...

Настоящий HTTP-заголовок или явный заголовок в теле #routing: важнее строки без префикса.

Глобально и по подписке

Пользователь выбирает один активный профиль в настройках — это глобальный выбор. Подписка может его переопределить: если в ней указан профиль (в её настройках или заголовком routing-profile), при подключении к её серверам применяется он. И то, и другое можно выключить: глобально в настройках (или ссылкой routing/off), а для подписки — вариантом «Не применять».

Какой профиль победит

При обновлении подписки несколько заголовков выше могут прийти одновременно. Они разбираются в таком порядке:

1 routing / autorouting — профиль, присланный целиком, важнее скачанного по routing-update-url. Присылайте сам профиль, если хотите быть уверены, какая именно версия окажется у клиента.
2 только routing-update-url — если профиль не пришёл целиком, клиент скачает его по этому адресу и импортирует. Заголовок работает сам по себе — routing рядом не нужен.
3 routing-profile (имя) — определяет, каким профилем подписка пользуется, и перебивает всё, что пришло в этом обновлении. Если указать Corporate и одновременно прислать профиль Default, подписка возьмёт Corporate, а Default всё равно попадёт в список.

Имя, которому ничего не соответствует, ничего не меняет. Если пришёл routing-profile: Corporate, а профиля с таким именем на устройстве нет, подписка сохранит прежние настройки маршрутизации. Клиент не подставит другой профиль и не сбросит настройку — опечатка в заголовке не может незаметно перенаправить трафик ваших пользователей.

Глобальный выбор не трогается никогда. Всё описанное выше задаёт профиль для этой подписки. Профиль, выбранный пользователем в настройках, остаётся как есть и продолжает действовать для всех остальных подписок. Чтобы сменить профиль подписки, используйте routing-profile; заголовка, меняющего глобальный выбор пользователя, нет.

Изменения применяются при следующем подключении. Профили читаются при сборке конфигурации туннеля, поэтому профиль, пришедший на активном соединении, начнёт действовать после переподключения — обновление подписки на ходу не перенаправляет живой трафик.

Какой резолвер используется, по порядку
заголовок s-resolve → поле RemoteDNSDomain активного профиля маршрутизации → настройка пользователя → встроенный список (если пользователь его включил) → системный резолвер.
s-resolve: off отключает предразрешение целиком — профиль в этом случае не подхватится.
↑ К оглавлению
08

Обход блокировок

ЗАЧЕМ ЭТО НУЖНО

Системы DPI часто блокируют по SNI (домен назначения) из TLS ClientHello — первого пакета каждого TLS/Reality-хендшейка. Фрагментация дробит этот ClientHello на множество мелких TCP-сегментов с крошечными задержками, так что DPI не может собрать SNI обратно → не видит домен → не может резать по домену. Работает целиком на штатном ядре Xray (outbound freedom с настройками fragment) — без дополнительного сервера и инфраструктуры.

Четыре параметра ниже решают разные задачи и часто применяются вместе.

Приложение настраивает всё за вас. Вручную добавлять fragment-outbound в конфиг НЕ нужно: при включении приложение само вставляет его и перенаправляет через него весь прямой TLS/Reality-трафик на ЛЮБОМ конфиге, оставляя цепочки (multi-hop) нетронутыми — фрагментируется только тот хоп, который видит DPI.

08.1 · s-fragment — фрагментация TLS

Включение — заголовок s-fragment

Значение
Эффект
отсутствует / off
Выключено (отсутствие заголовка не меняет прежнее значение)
on
Включено с параметрами по умолчанию
packets=tlshello;length=50-100;interval=10-20;maxsplit=100-200
Включено с этими параметрами

Передаётся как HTTP-заголовок ответа s-fragment: или строкой-комментарием в теле #s-fragment: packets=tlshello;length=50-100;interval=10-20.

🔒 Требует активного Provider ID. Как и любое управляемое провайдером поле, s-fragment учитывается только пока ваш Provider ID активен. Неактивный или неизвестный провайдер не может включать фрагментацию на устройствах пользователей.

Параметры

Параметр
Формат · по умолчанию · описание
packets
tlshello или N-M · по умолчанию tlshello
Какие пакеты фрагментировать. tlshello — только TLS ClientHello (несёт SNI), рекомендуется. 1-3 — первые 1–3 исходящих пакета.
length
min-max (байты) · по умолчанию 50-100
Размер фрагмента, случайно в диапазоне. Меньше = труднее собрать, но больше накладных расходов.
interval
min-max (мс) · по умолчанию 10-20
Задержка между фрагментами, случайно в диапазоне. Джиттер мешает буферизации и пересборке.
maxsplit
min-max · по умолчанию не задан
Опционально (только новые ядра). Ограничивает число фрагментов. Старые ядра игнорируют.

Значения по умолчанию подходят большинству сетей. Тюнинг под конкретный DPI: сильнее блокируют → меньше length и/или больше interval; падает скорость → больше length и меньше interval.

Имена параметров не зависят от регистра (maxsplit и maxSplit — один и тот же ключ), а ключ packets — во множественном числе: packets=, не packet=. packet= относится к s-noise, где означает полезную нагрузку.

⚙️Пользователь может включить сам

Пользователь также может включить фрагментацию и отредактировать те же параметры вручную — в приложении: Настройки → Обход блокировок → TLS-фрагментация, независимо от провайдера. При ручном включении его параметры имеют приоритет над заголовком.

08.2 · s-noise — шумовые пакеты

Мусорный трафик, отправляемый до соединения, чтобы DPI не распознал его начало. Работает в паре с фрагментацией — у них общий выход, и вместе они закрывают и начало соединения, и его содержимое.

Значения: on / 1 — параметры по умолчанию (rand, пакет 50-100, задержка 10-20); off / 0 — выключить. Полная форма — type=rand;packet=50-100;delay=10-20 или позиционно rand,50-100,10-20; type принимает rand, str или hex.

Принимаются оба написания заголовка — s-noise и s-noises.

08.3 · s-resolve — предразрешение адреса сервера

Разрешает адрес сервера через DoH до подъёма туннеля. Нужно там, где локальный DNS отдаёт подменённый ответ для домена вашего узла: без этого клиент получит неверный IP и просто не подключится.

Значения: on / 1 — резолвер по умолчанию (dnsforge.de); off / 0 — выключить; URL задаёт свой резолвер.

Если отравлен и домен самого резолвера, добавьте bootstrap-IP через ;ip=… — например https://dnsforge.de/dns-query;ip=49.12.67.122. Исходный домен остаётся в SNI, поэтому TLS/Reality проверяются как обычно. Если резолвер не ответил за полторы секунды, используется обычный поиск — параметр может только помочь, но не заблокировать подключение.

Несколько резолверов. Адреса можно перечислить через запятую, каждый со своим bootstrap-IP — они пробуются по порядку до первого ответившего. Одиночный адрес работает как раньше: это тот же формат без запятых.

s-resolve: https://a.example/dns-query;ip=1.2.3.4, https://b.example/dns-query;ip=5.6.7.8

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

08.4 · s-dns — DNS-over-HTTPS в туннеле

Шифрует обычные DNS-запросы внутри туннеля. Значения: on / 1 — встроенный резолвер по умолчанию (dnsforge.de); URL вида https://dnsforge.de/dns-query — свой резолвер; off / 0 — выключить. Разрешённый DNS идёт через туннель, а не через физическую сеть.

Порядок применения
Параметры провайдера всегда важнее пользовательских настроек, включая off. Если настраивает сам пользователь, порядок такой: фрагментация → шум → DoH.
↑ К оглавлению
09

WireGuard/AmneziaWG

Серверы WireGuard и AmneziaWG работают как любые другие: стоят в одном списке рядом с VLESS/Trojan/Shadowsocks, пользователь выбирает их так же. Отдельного режима нет, дополнительной настройки нет. Работает на штатном ядре Xray (outbound wireguard) — со стороны провайдера ничего нового не требуется, кроме самого конфига.

Способ А — в JSON-подписке

Обычный конфиг Xray, где у outbound "protocol": "wireguard":

{ "remarks": "🇳🇱 Нидерланды — WG", "outbounds": [{ "tag": "proxy", "protocol": "wireguard", "settings": { "secretKey": "<приватный ключ клиента>", "address": ["10.0.0.2/32"], "mtu": 1420, "peers": [{ "publicKey": "<публичный ключ сервера>", "preSharedKey": "<необязательно>", "endpoint": "example.com:51820", "allowedIPs": ["0.0.0.0/0", "::/0"], "keepAlive": 25 }] } }] }

Адрес сервера, который показывается в приложении, берётся из peers[0].endpoint. У WireGuard нет блоков vnext или servers — строка endpoint единственное место, где живёт хост.

Поля AmneziaWG. Тот же outbound принимает параметры обфускации — добавьте их рядом с обычными. Они нужны только для AmneziaWG; для обычного WireGuard их просто не указывают (нулевые значения игнорируются, соединение ведёт себя ровно как раньше).

{ "protocol": "wireguard", "settings": { "secretKey": "…", "address": ["10.66.66.17/32"], "mtu": 1420, "jc": 4, "jmin": 40, "jmax": 70, "s1": 15, "s2": 25, "s3": 0, "s4": 0, "h1": 1111111, "h2": 2222222, "h3": 3333333, "h4": 4444444, "peers": [ { "publicKey": "…", "endpoint": "1.2.3.4:51820", "allowedIPs": ["0.0.0.0/0"] } ] } }
Поле
Что задаёт
jc
Сколько мусорных пакетов слать перед рукопожатием (4–12)
jmin / jmax
Границы размера мусорного пакета, байты (примерно 8–80)
s1 / s2
Размер префикса у init- и response-пакета (15–150)
s3 / s4
Размер префикса у cookie- и транспортных пакетов; необязательны — не указывайте, если сервер их не задаёт
h1–h4
Переписанные типы заголовков: без них пакеты опознаются по сигнатуре. Одно число или, для серверов AmneziaWG 3.1, диапазон "a-b" строкой ("h1": "1000-2000"); диапазоны применяются начиная с Android 428, iOS/macOS/tvOS 2.0.10 (315) и desktop 203 — старые сборки принимают только числа
s1s4 и h1h4 обязаны совпадать на сервере и в конфиге клиента, иначе соединение молча не работает: туннель поднимается, а трафик не идёт. jc, jmin и jmax совпадать не обязаны — каждая сторона шлёт свой мусор.

Версия сервера — 3.1 и ниже. Приложение собрано на ветке AmneziaWG 3.1 и совместимо со всеми предыдущими, обновлять на своей стороне ничего не нужно. Присылайте только те параметры, которые сервер действительно использует: всё, что не указано, остаётся по умолчанию и игнорируется.

Появилось в
Параметры
0.2.11 и ранее
jc, jmin, jmax, s1, s2, h1–h4
0.2.13
s3, s4
0.2.16
i1–i5
3.0
header_protection_key, content_padding_addition, rekey_after_time, rekey_timeout, reject_after_time, keepalive_timeout, max_handshake_attempts
3.1
random_trailers, disable_cookies, диапазоны a-b для h1–h4 и PersistentKeepalive

Параметр из 3.1, отправленный старому серверу, тот просто не поймёт — держите стороны в согласии: настраивайте клиент ровно тем, что работает на сервере.

Способ Б — ссылкой wireguard://

wireguard://<base64 стандартного .conf> — в base64 лежит обычный конфигурационный файл WireGuard, ровно тот текст, который выдают официальному клиенту. Необязательный якорь #Имя задаёт отображаемое название:

wireguard://W0ludGVyZmFjZV0K…#🇳🇱%20Нидерланды

Раскодированный payload — просто:

[Interface] PrivateKey = <приватный ключ клиента> Address = 10.0.0.2/32 DNS = 1.1.1.1 [Peer] PublicKey = <публичный ключ сервера> PresharedKey = <необязательно> Endpoint = example.com:51820 AllowedIPs = 0.0.0.0/0, ::/0

Эта форма работает везде, где и остальные share-ссылки: вставка в форму добавления, внутри зашифрованной crypt1-ссылки, отдельной строкой в теле подписки.

Сборка ссылки без кода

Вставьте .conf на служебной странице — она соберёт wireguard:// и QR прямо в браузере, без запросов наружу.

Открыть сборщик ссылок →

Ключи уходят в ядро как есть — приложение их не перекодирует.

Импорт на стороне клиента

Начиная с Android 379, iOS/macOS 2.0.3 (277+) и десктопа 169 экран добавления принимает конфиг WireGuard/AmneziaWG четырьмя способами — раздавать пользователям можно любым из них:

1Файл .conf — кнопка «Выбрать файл конфигурации» (Android, iOS, macOS, Windows, Linux; на Apple TV кнопки нет — там добавление «с телефона»).
2Голый текст конфига — содержимое [Interface]…[Peer] вставляется прямо в поле ввода, на всех платформах.
3Ссылка, отдающая конфиг — https-адрес, по которому лежит сам текст .conf. Многие панели раздают конфиги именно так.
4Ссылка-схема и QRwireguard:// / amneziawg:// / awg:// / wg:// с base64 конфига, как описано выше.

Параметры маскировки AmneziaWG (Jc, Jmin, Jmax, S1, S2, H1H4) подхватываются из [Interface] во всех четырёх способах. В более старых сборках работают только схема-ссылка и QR.

C. AmneziaWG — WireGuard с обфускацией

AmneziaWG — это тот же WireGuard плюс обфускация: мусорные пакеты и переписанные типы заголовков, из-за которых DPI не распознаёт хендшейк WireGuard.

Схемы ссылок. wireguard://, amneziawg://, awg:// и wg:// принимаются и разбираются одинаково — полезная нагрузка это base64 обычного .conf, с необязательным фрагментом #Name.

amneziawg://<base64url файла .conf>#Germany

Параметры обфускации живут в секции [Interface] этого .conf, рядом с обычными ключами:

[Interface] PrivateKey = … Address = 10.66.66.17/32 MTU = 1420 Jc = 4 ; сколько мусорных пакетов отправить до хендшейка Jmin = 40 ; размер мусорного пакета, нижняя граница Jmax = 70 ; размер мусорного пакета, верхняя граница S1 = 15 ; размер префикса init-пакета S2 = 25 ; размер префикса response-пакета S3 = 0 ; префикс cookie-пакета — необязательно, если сервер не задаёт S4 = 0 ; префикс транспортного пакета — то же H1 = 1111111 ; переписанные типы заголовков — иначе узнаются по сигнатуре (серверы 3.1 могут задавать диапазон: H1 = 1000-2000) H2 = 2222222 H3 = 3333333 H4 = 4444444 [Peer] PublicKey = … Endpoint = 194.61.120.25:57932 AllowedIPs = 0.0.0.0/0,::/0

Не указывайте их — получите обычный WireGuard: приложение и ядро ведут себя точно как раньше. То же поле за полем: присылайте только то, что работает на вашем сервере. Подходит любая версия AmneziaWG, 3.1 и старше — см. таблицу версий в разделе A.

🛠️
Соберите ссылку из .conf
Генератор принимает и конфиги AmneziaWG — параметры обфускации переносятся как есть. Всё считается в браузере.
Генератор ссылок →

Несколько локаций в одном файле. URL подписки может вернуть JSON-контейнер вместо ссылок:

{ "type": "amneziawg", "version": 1, "servers": [ { "name": "Germany", "config": "<base64url файла .conf>" }, { "name": "Netherlands", "config": "<base64url файла .conf>" } ] }

type принимается как amneziawg, awg, wireguard или wg. Некорректная запись пропускается, не ломая остальной список.

Заметки для операторов

  • IPv6-endpoint поддерживается, пишется стандартно: [2606:4700:d0::a29f:c001]:2408.
  • keepAlive (секунды) — persistent keepalive. Ставьте, если клиенты за NAT: без него трансляция истекает и туннель замолкает в одну сторону.
  • MTU в конфиге управляет тем, что шлёт клиент. Что шлёт сервер, задаётся MTU интерфейса wg0 на ноде — настраивайте оба, иначе путь, не несущий полноразмерные пакеты, пропустит хэндшейк и встанет.
  • Маршрутизация на ноде важна: ip route get <ip клиента> должен резолвиться через wg0. Если указывает на дефолтный шлюз — хэндшейк всё равно пройдёт (демон отвечает на него напрямую), но данные до клиента не дойдут никогда. Выглядит как блокировка, но это не она.
↑ К оглавлению
10

Форматы ссылок

Всё, что понимает клиент, — в одном месте. Регистр схемы не важен.

Ссылки на серверы

Схема
Что внутри
vless:// vmess:// trojan:// ss:// socks://
Стандартные share-ссылки, как их выдаёт любая панель
wireguard:// · amneziawg:// · awg:// · wg://
base64 обычного файла WireGuard .conf — того же текста, что принимает официальный клиент — или base64 JSON-настроек в стиле xray
hysteria2:// · hy2://
Принимается и сохраняется, но пока не поддерживается ядром xray — такой сервер не подключится

Base64 читается снисходительно — обычный и URL-safe алфавит, с выравниванием и без.

#fragment задаёт отображаемое имя (percent-encoded). Он же может нести описание сервера — см. раздел 04a.

Что показывает «Посмотреть конфиг»

Правило одно: если в ссылке base64 — показывается расшифрованное содержимое, иначе ссылка как есть. Поэтому wireguard://<base64> покажет читаемый .conf, а vless://uuid@host:443?… останется ссылкой — расшифровывать нечего. JSON выводится с отступами.

Подписки

По адресу подписки может вернуться:

обычный текст — по одной ссылке в строке;
base64 того же списка;
JSON — полный конфиг xray или контейнер с несколькими серверами WireGuard/AmneziaWG:
{"type":"amneziawg","version":1,"servers":[ {"name":"Germany","config":"<base64 of .conf>"}]}

type принимается как amneziawg, awg, wireguard или wg. Битая запись пропускается, а не рушит всю подписку.

Зашифрованные значения заголовков

Значения заголовков можно передавать в закрытом виде — чтобы ссылки и настройки не расходились в открытую. Любой заголовок подписки может нести значение в зашифрованном виде — с тем же префиксом crypt1/, что и deep-links:

routing-update-url: crypt1/<base64> routing: crypt1/<base64> autorouting: crypt1/<base64> s-dns: crypt1/<base64> s-fragment: crypt1/<base64> s-resolve: crypt1/<base64> s-noise: crypt1/<base64> new-url: crypt1/<base64> s-siteurl: crypt1/<base64>

Клиент отрезает префикс, расшифровывает и дальше работает с результатом так же, как если бы он пришёл открытым текстом. Без префикса ничего не меняется — существующие подписки продолжают работать как есть. Шифрование необязательное и по-заголовочное: в одном ответе можно свободно смешивать зашифрованные и открытые значения.

Шифрование — ровно то же, что у ссылок smproxy://crypt1/…: AES-256-GCM, nonce || ciphertext || tag, base64, под тем же общим ключом. Панели, которая уже выдаёт такие ссылки, новый код не нужен: зашифруйте значение, добавьте префикс crypt1/ и положите в заголовок.

Чтобы получить зашифрованное значение вообще без кода, используйте тот же генератор, что делает ссылки — он вернёт smproxy://crypt1/<encrypted>; возьмите всё после smproxy://, это и есть crypt1/<base64>, что ждёт заголовок.

POST https://provider.smproxy.io/public/crypto-link { "url": "https://example.com/routing.json" } → { "link": "smproxy://crypt1/AbCd…" } routing-update-url: crypt1/AbCd… s-dns: crypt1/AbCd…
Генератор зашифрованных ссылок →

Принимаются оба разделителя — crypt1/<base64> и crypt1:<base64> — но используйте слэш: так же, как в deep-links, и формат один на всё.

Это обфускация, а не секретность. Ключ зашит в каждом клиенте, поэтому любой, кто разберёт приложение, прочитает эти значения. Это убирает адрес профиля с глаз — от беглого взгляда на трафик или от пересылки в чат — и не более того. Не прячьте так ничего действительно чувствительного.

Deep-links

Принимаются две схемы: smproxy:// (текущая) и smartvpn:// (до переименования, оставлена рабочей для уже выданных ссылок). Это касается только кликабельных ссылок — систему открывает приложение, зарегистрированное на эту схему. Внутри заголовков routing / autorouting принимается любое слово схемы (раздел 07a).

Ссылка
Действие
smproxy://add?url=<encoded URL>
Добавить подписку
smproxy://add/<https://…> · add/<base64 URL>
То же, в виде пути
smproxy://crypt1/<base64>
Добавить из зашифрованной полезной нагрузки
smproxy://routing/add/<base64|url>
Импортировать профиль маршрутизации, не переключаясь на него
smproxy://routing/onadd/<base64|url>
Импортировать и включить
smproxy://autorouting/add|onadd/<url>
То же плюс обновление с этого адреса
smproxy://routing/off
Выключить маршрутизацию

Полезная нагрузка может передаваться и как ?data=<base64>. Для ссылок маршрутизации по адресу может лежать JSON профиля, его base64 или другая ссылка smproxy:// — разрешаются все три варианта.

↑ К оглавлению
11

Кабинет провайдера

Самообслуживание на provider.smproxy.io — регистрация, статистика, сообщения и генераторы ссылок.

URL
Назначение
provider.smproxy.io/provider/register
Регистрация (email + пароль или Google)
provider.smproxy.io/provider/login
Вход
provider.smproxy.io/provider/dashboard
Ваш Provider ID + установка своего ID
provider.smproxy.io/provider/stats
Статистика использования
provider.smproxy.io/provider/messages
Составление внутренних сообщений (раздел 06)
provider.smproxy.io/provider/hwid-link
Генерация device-bound ссылки smproxy://addhw/…
provider.smproxy.io/provider/crypto-link
Генерация зашифрованной ссылки smproxy://crypt1/…
Заведите аккаунт провайдера

Регистрация занимает минуту и даёт Provider ID, статистику и рассылку сообщений. Бесплатно.

Статистика за 3 шага
1Зарегистрируйтесь
2Скопируйте свой Provider ID (или задайте свой)
3Добавьте его в подписку как заголовок ответа providerid / s-providerid

Приложения ваших пользователей начнут наполнять статистику, которую вы видите в кабинете.

↑ К оглавлению
12

Стоимость — сейчас всё бесплатно

SMProxy App находится на этапе активного развития и открытого тестирования, поэтому сейчас все функции бесплатны и останутся такими в течение длительного времени: импорт подписок, все заголовки раздела 04, device-bound (addhw) и зашифрованные (crypt1) ссылки, авто-обновление, kill-switch, split-tunnel, темы, статистика и сообщения/push. Платных тарифов и закрытых функций нет.

Импорт подписок Все заголовки ответа addhw и crypt1 Авто-обновление Kill-switch Split-tunnel Темы Статистика Сообщения / push
🌱 В будущем, когда проект выйдет из стадии тестирования, часть возможностей может измениться. Мы заранее и открыто предупредим о любых изменениях — то, что доступно сейчас, останется бесплатным на всё время бета-периода.

🔒Поля под гейтом provider_active

Ключи с замком — это брендинг/поведение, управляемые провайдером (кнопки сайта/Telegram, UI продления, fallback-url, миграция и hide-settings). Они применяются, только когда в ответе есть providerid и у него provider_active == true.

Если провайдер не активен (нет providerid, неизвестен или provider_active == false) — эти поля принудительно сбрасываются на каждом обновлении (чтобы чужой/устаревший провайдер не мог подменить кнопки или fallback). Восстанавливаются, как только провайдер снова активен.

Без гейта (работают всегда): profile-title, s-title, profile-update-interval, subscription-userinfo, announce, support-url, profile-web-page-url, providerid, geoipurl/geositeurl.

↑ К оглавлению
13

Что требует активного Provider ID

Одно место вместо поиска значков 🔒. Активный — значит Provider ID, присланный с подпиской, принадлежит действующему аккаунту провайдера. Без этого ключи из первой таблицы просто игнорируются, сама подписка продолжает работать.

🔒Требует активного Provider ID

Область
Ключи
Обход блокировок
s-fragment s-noise s-noises s-resolve s-dns — и их аналоги из INCY/Happ fragmentation-* noises-* server-address-resolve-*
Переезд и запасные адреса
new-url new-domain fallback-url fallback-domains
Брендинг провайдера
s-siteurl s-sitename s-tgbot x-tgbot sort-order serverDescription
Баннер и контакты
banner-text banner-button-text banner-button-url banner-bg-color banner-button-color announce-url support-email
Интерфейс окончания подписки
sub-expire s-showexpire sub-expire-button-text sub-expire-button-link-site sub-expire-button-link-tg notification-subs-expire
Доступ
hide-settings

Работает и без Provider ID

Ключ
Примечание
profile-title / s-title
Название подписки
profile-update-interval
Интервал автообновления
profile-web-page-url
Веб-страница провайдера
announce
Сам текст объявления — но announce-url (кликабельность) требует активного провайдера
support-url
Ссылка на поддержку
subscription-userinfo
Квота и дата окончания
subid
Стабильный id подписки
routing autorouting routing-update-url routing-update-interval routing-profile
Профили маршрутизации, включая формы deep-link-команд — профиль несёт только правила, активный провайдер не нужен
geoipurl / geositeurl
Адреса гео-баз — только в ответе подписки

Гейт работает одинаково независимо от того, как пришло значение: HTTP-заголовок, строка #ключ: в теле или кабинет провайдера.

↑ К оглавлению
Справочник отражает текущее поведение приложения. Точные правила формата/base64 для отдельных заголовков сверяйте с сервером перед публикацией.