Skip to content

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.

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 providersee 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-url wins.

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-settings1/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.