Subscription metadata¶
The subscription endpoint response is the body (server list) plus metadata (branding, expiry, updates). Metadata can be delivered two ways, and the client reads both.
AHTTP response header
Profile-Title: My VPN
BComment line in the body
#profile-title: My VPN
★
The HTTP header wins. A #key: value line in the body is used only if there's no header of the same name. This lets you deliver metadata when you can't set headers (static hosting, Telegram files, etc.).
⇄
On/off values. Any header that switches a feature on or off accepts a family of equivalents, case-insensitive: 1 / on / true / yes all mean enabled, and 0 / off / false / no all mean disabled.
⚠
Bypass features: your setting wins over the user's. For all four bypass headers — s-fragment, s-dns, s-noise, s-resolve — the value you send takes precedence over the user's own toggle, off included. The user's toggle only applies when you send nothing at all. This is deliberate: these parameters have to match what your server expects, and a user switching them on their own would simply break the connection.
Live subscription updates¶
A provider can change a subscription's bypass settings and routing without waiting for the next subscription re-fetch: changes made in the provider account reach online devices within minutes and are applied without reconnecting — new values take effect on the next connect. The same rules apply as for the HTTP headers above — in particular, they are honored only while the provider is active. The delivery mechanism is internal to the app: nothing needs to be implemented on the subscription endpoint.
Body line syntax¶
-
Starts with # , then a key [A-Za-z0-9-]+ , then the value.
-
The colon is optional — #profile-title My VPN also works.
-
The key is case-insensitive.
-
The first occurrence of a key wins; duplicates are ignored.
-
The body is scanned both as-is and after full base64 decoding — it works for plain text and for a single base64 blob (the usual vless-link format).
-
Only the keys in the table below are read from the body; lines with other keys are ignored.
base64 for non-ASCII (e.g. Cyrillic)
Text fields can be encoded by prefixing base64: — works in both header and body. Applies to profile-title, s-title, announce, sub-expire-button-text.
#profile-title: base64:TXkgVlBO → “My VPN”
Keys¶
Each key works both as a header Key: value and as a body line #key: value. All free.
Branding & links¶
| Key (header / #body) | Purpose |
|---|---|
profile-title / s-title🔒 |
Subscription title (text or base64:) |
s-sitename🔒 |
Provider/site name |
s-siteurl🔒 |
Provider website URL |
s-tgbot / x-tgbot🔒 |
Telegram bot/channel link |
support-url🔒 |
Support link |
announce🔒 |
Announcement banner text (text or base64:), up to 5 lines — longer text is ellipsized |
announce-url🔒 |
Makes the announcement clickable — opens this URL |
support-email🔒 |
Support email — adds an “Email support” button to the subscription card |
profile-web-page-url🔒 |
Provider web page |
Subscription status & renewal¶
| Key (header / #body) | Purpose |
|---|---|
subscription-userinfo🔒 |
upload=…; download=…; total=…; expire=… (quota + expiry epoch) |
sub-expire / s-showexpire🔒 |
Show expiry status |
sub-expire-button-text🔒 |
Renewal button label (text or base64:) |
sub-expire-button-link-site🔒 |
Renewal button → website |
sub-expire-button-link-tg🔒 |
Renewal button → Telegram |
notification-subs-expire🔒 |
1 — send “subscription expires in N days” notifications, starting 3 days before expiry. Independent of sub-expire / s-showexpire (does not control the renewal buttons and does not depend on them). Absent = no notifications. Also accepted as #notification-subs-expire: 1 in the body. |
Updates & data¶
| Key (header / #body) | Purpose |
|---|---|
profile-update-interval🔒 |
Subscription auto-update interval (hours) |
fallback-url🔒 |
Fallback subscription URL |
geoipurl / geositeurl🔒 |
GeoIP / GeoSite database sources (section 07) |
sort-order🔒 |
Order of servers in the list: ping (fastest first, by measured latency — unmeasured go last), name (alphabetical) or none (as sent, the default). Unknown values are treated as none. |
Migration & access¶
| Key (header / #body) | Purpose |
|---|---|
new-url🔒 |
Fully replace the subscription URL (migration) — see below |
new-domain🔒 |
Replace only the URL host: host or host:port (safe for personal links). Can also be driven from the provider portal without touching your panel — see section 05b |
fallback-domains🔒 |
Backup hosts, comma-separated. If the subscription host stops answering, the app tries these in order, keeping each user's own path and token. Unlike new-domain this changes nothing permanently — a way out when your main address is blocked, not a migration. Set it from the provider portal or send it yourself (section 05b) |
hide-settings🔒 |
Hides the subscription URL and its config from the user — full details in section 04b. 1 hides, 0 un-hides; an absent header changes nothing. |
Identification¶
| Key (header / #body) | Purpose |
|---|---|
providerid / s-providerid🔒 |
Provider ID (section 05). The s- variant is preferred; also read from the URL ?providerid= |
subid🔒 |
Stable subscription ID — lets you change the subscription URL without your users ending up with duplicates (section 05a) |
Bypass¶
| Key (header / #body) | Purpose |
|---|---|
s-noise🔒 |
Noise packets before the handshake — junk traffic sent ahead of the connection so DPI can't recognise its start. Works alongside s-fragment (they share the same egress). on / 1 uses the defaults (rand, packet 50-100, delay 10-20); off / 0 disables. Full form: type=rand;packet=50-100;delay=10-20, or positional rand,50-100,10-20; type is rand, str or hex. → full details in section 08 |
s-resolve🔒 |
Pre-resolve the server address over DoH before the tunnel comes up — for networks where the local DNS forges the answer for your node’s domain. on / 1, off / 0, or a resolver URL with an optional bootstrap IP. → full details in section 08 |
s-fragment🔒 |
TLS ClientHello fragmentation against SNI-based DPI (full reference — section 08). |
s-dns🔒 |
DNS-over-HTTPS inside the tunnel: on / 1 — the built-in default resolver (dnsforge.de); a URL — that resolver; off / 0 — disable. → full details in section 08 |
🔒 Locked keys work only with an active provider — see the full list.
Provider banner — banner-*¶
A banner on the subscription card, shown only while the provider id is active (INCY-compatible). Colors must be #RRGGBB — anything else is ignored and the app's own theme color is used. The button caption color is picked automatically from the button's brightness, so it stays readable on light and dark backgrounds alike.
| Header | Value & purpose |
|---|---|
banner-text🔒 |
text or base64: — banner body, up to 5 lines |
banner-button-text🔒 |
text or base64:, ≤ 25 characters — button caption |
banner-button-url🔒 |
a URL or a deep link — where the button leads |
banner-bg-color🔒 |
#RRGGBB — banner background |
banner-button-color🔒 |
#RRGGBB — button background |
Bypass parameters at a glance¶
A summary of the four bypass headers — the detailed reference for each is in section 08.
| Header | Values |
|---|---|
s-fragment |
on | off | packets=<tlshello\|1-3\|all>;length=<min-max>;interval=<min-max>[;maxsplit=<n>] · default when only on is sent: packets=tlshello;length=50-100;interval=10-20 |
s-noise (s-noises) |
on | off | type=<rand\|str\|hex>;packet=<value>;delay=<min-max> · default when only on is sent: type=rand;packet=50-100;delay=10-20 |
s-resolve |
on | off | <doh-url>[;ip=<bootstrap>][, …] · default when only on is sent: the built-in resolver |
s-dns |
on | off | <doh-url> · default when only on is sent: the built-in resolver (dnsforge.de) |
Compatibility with INCY / Happ header names¶
If you already configure another client, the app also understands their atomic keys and folds them into the equivalent setting. Our s-* header always wins — the keys below are read only when the matching s-* header is absent. *-enable: 0 means explicitly off.
| Their keys | Mapped to |
|---|---|
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> becomes type=rand;packet=<n>. noises-rand-range is not supported.
Where a value can come from, and what wins: 1) the HTTP response header — highest priority; 2) a #key: value line in the body (and, for routing commands, a bare *://routing/… line) — used when the header is absent; 3) the app's own setting — when the provider sends nothing. For the four bypass headers the provider's value wins over the user's toggle, off included.
Text values accept plain UTF-8 or a base64: prefix; any header value may also arrive crypt1/-encrypted.
URL migration: new-url / new-domain¶
Re-point the saved subscription URL — the provider can move without the user having to re-add the subscription.
-
new-url— replaces the whole URL (any format), the same for all subscriptions. -
new-domain— changes only the host (scheme, port, path, query are kept); each subscription keeps its own path/token — safe for personal URLs. If both are sent,new-urlwins.
Applies only if the same response contains providerid AND that provider has provider_active == true. All local subscriptions with that providerId are migrated. The locked flag is preserved: a subscription from crypt1/addhw stays locked on the new URL.
Compatibility aliases (Happ / INCY)¶
Headers other clients use are read as their SMProxy counterparts when the SMProxy header is absent — a panel written for Happ or INCY works without changes. Aliases are accepted both in HTTP headers and in the #key: value body form.
| Other client sends | Read as |
|---|---|
subscription-name |
profile-title — INCY fallback name |
content-disposition: attachment; filename="x.txt" |
profile-title — filename without .txt / .yaml / .yml; lowest priority |
homepage |
profile-web-page-url — INCY |
hide-url |
hide-settings — 1/true hides, 0/false un-hides |
sub-info-text |
banner-text — Happ “advanced announcements”; 0 = no banner |
sub-info-button-text / sub-info-button-link |
banner-button-text / banner-button-url |
sub-info-color (red · blue · green) |
banner-bg-color — mapped to a hex colour |
sub-expire-button-link |
sub-expire-button-link-tg for t.me / tg://, otherwise sub-expire-button-link-site — Happ single-link form |
routing-enable: 0 |
routing: routing/off — Happ “disable routing” |
subscription-userinfo: 0 |
header absent — INCY “hide the traffic block” |
expire=<milliseconds> in subscription-userinfo |
seconds — values above 32 000 000 000 are treated as ms |
The SMProxy header always wins when both are present.