Rameta

For VPN providers

Moving users from Happ to Rameta

Rameta understands Happ subscriptions as-is: same request and response headers, same body formats and links. The Remnawave HWID limit works unchanged. Below is everything you need so users can simply tap “Open in Rameta”.

1. Quick start

0 minutes

Do nothing: Remnawave subscriptions already work in Rameta (the “Fallback Base64” rule returns links).

2 minutes

Add the Rameta response rule → users get full Xray JSON with all transport settings. →

Button

Add a button to your subscription page with rameta://add/{{SUBSCRIPTION_LINK}}.

Branding

Register and send the header providerid — logo, color and routing profile in the app. →

2. What Rameta sends

Every subscription update is a plain GET with these headers:

HeaderExamplePurpose
User-AgentRameta/1.0.0/ios/42Rameta/<version>/ios/<build>. In Remnawave rules use the regex ^Rameta/.
X-HWID5F0C2A4E-…Stable device id (kept in Keychain). Remnawave counts devices by it — the HWID device limit works just like with Happ.
X-Device-OSiOSPlatform (shown in the Remnawave device list).
X-Ver-OS18.1iOS version.
X-Device-ModeliPhone16,2Device model.
X-Device-LocaleruUI language — lets you localize titles and announcements.
X-App-Version1.0.0Rameta version.
X-Age-Public-Keyage1pq1…Per-device age public key (hybrid post-quantum ML-KEM-768 + X25519). Lets you encrypt the body for this device only.
Remnawave reads x-hwid, x-device-os, x-ver-os and x-device-model — the HWID Device Limit counts Rameta devices exactly like Happ. When the limit is reached the panel answers with x-hwid-max-devices-reached: true and an announce, and Rameta shows it to the user.

3. Response headers

Same set and values as Happ. In Remnawave they live in “Subscription settings → custom response headers” and reach Happ and Rameta alike.

HeaderMeaning
profile-titleSubscription name. Plain text or base64:… (UTF-8).
subscription-userinfoupload=…; download=…; total=…; expire=<unix> — traffic and expiry.
profile-update-intervalAuto-update interval, hours.
support-urlSupport link (https://, tg://, mailto:).
profile-web-page-urlSubscription / account web page.
announceAnnouncement up to 200 chars; base64:… allowed.
routingRouting profile link: happ://routing/… or rameta://routing/… (add — offer, onadd — enable immediately, off — disable).
provideridProvider id from the Rameta registry (register) — the app loads your logo, color, announcement and routing profile.
hide-settings1 — hide server settings of this subscription from the user.
sub-info-color · sub-info-text · sub-info-button-text · sub-info-button-linkSubscription info banner: color (red/blue/green), text, button and its link.
sub-expire · sub-expire-button-linkWarn about expiry and where the “Renew” button leads.
new-urlPermanently replace the subscription URL.
new-domainReplace only the domain of the subscription URL.
fallback-urlBackup URL used when the main one fails.
subscription-autoconnect · subscription-autoconnect-typeAuto-connect after update and how the server is chosen.
subscription-auto-update-enableEnable/disable auto-update.
ping-type · check-url-via-proxyLatency test method and the URL tested through the proxy.
fragmentation-* · noises-* · mux-*TLS ClientHello fragmentation, noises and mux for share links — same keys and values as Happ.
x-hwid-max-devices-reached · x-hwid-not-supported · x-hwid-limitSent by Remnawave when the device limit is hit — Rameta shows a clear message and the announce.

The same keys may also be sent in the body as leading #key: value lines, and in the URL fragment:

https://sub.example.com/abcdef#Мой VPN?profile-update-interval=6&support-url=https://t.me/myvpn_support

If a key is set in several places, the body line wins, then the response header, then the URL fragment (same as Happ).

4. Subscription formats

vless://uuid@de1.example.com:443?type=tcp&security=reality&sni=www.microsoft.com&pbk=…&sid=…&fp=chrome&flow=xtls-rprx-vision#🇩🇪 Germany
trojan://password@nl.example.com:443?security=tls&sni=nl.example.com#🇳🇱 Netherlands
ss://YWVzLTEyOC1nY206cGFzcw@fi.example.com:8388#🇫🇮 Finland
hy2://auth@jp.example.com:443?sni=jp.example.com#🇯🇵 Japan

Body with metadata lines:

#profile-title: base64:0JzQvtC5IFZQTg==
#profile-update-interval: 6
#support-url: https://t.me/myvpn_support
#announce: Новые серверы в Казахстане!
vless://…#🇩🇪 Germany
vless://…#🇳🇱 Netherlands

5. Remnawave

Response Rule for Xray JSON

Panel → Subscription → Response Rules. Add this rule after “Browser Subscription” and before “Fallback Base64” (rules are checked top-down, the first match wins):

{
  "name": "Rameta",
  "enabled": true,
  "operator": "AND",
  "conditions": [
    {
      "headerName": "user-agent",
      "operator": "REGEX",
      "value": "^Rameta/",
      "caseSensitive": true
    }
  ],
  "responseType": "XRAY_JSON",
  "responseModifications": {
    "additionalExtendedClientsRegex": [
      "^Rameta/"
    ]
  }
}

Subscription page button

In the Remnawave Subscription Page settings add Rameta for iOS with an “add subscription” button:

rameta://add/{{SUBSCRIPTION_LINK}}

The App Store link will be published at release; until then use TestFlight.

6. Encrypted subscriptions

Remnawave can encrypt the response body with age. Add encryption with the Rameta public key (method age1, X25519) to the Rameta rule:

{
  "additionalExtendedClientsRegex": [
    "^Rameta/"
  ],
  "encryption": {
    "method": "age1",
    "key": "age1zaymjnnxuh8xsmrw9cjajm4gqnjsgtxf6e8v54zx3rpejqsluyls0eymfz"
  }
}

Rameta public key: age1zaymjnnxuh8xsmrw9cjajm4gqnjsgtxf6e8v54zx3rpejqsluyls0eymfz (always current at /v1/app/config → agePublicKey).

A custom backend can also encrypt per device with the key from X-Age-Public-Key (age1pq1…). Remnawave uses a static key per rule, so use age1 with the Rameta key there.

Honestly: the Rameta secret key ships inside the app. Encryption hides the subscription from casual viewing (browser, link previews, forwarding, other clients) but not from someone reverse-engineering the app.
LinkAction
rameta://add/<url>Add a subscription. The URL may be raw or URL-encoded.
rameta://crypt/<payload>Add a subscription from an encrypted link (below).
rameta://routing/add/<base64>Offer a routing profile (base64 JSON).
rameta://routing/onadd/<base64>Add a profile and enable it immediately.
rameta://routing/offDisable the routing profile.
rameta://connect · rameta://disconnect · rameta://toggleConnect, disconnect, toggle the VPN (handy for iOS Shortcuts).
happ://add/… · happ://routing/…Happ links pasted from the clipboard or a QR code are accepted too.

8. Routing profiles

The format is Happ-compatible: the JSON below, base64-encoded, is delivered as rameta://routing/add/… (or onadd), via the routing header or your provider card.

KeyMeaning
NameProfile name.
GlobalProxy"true" — everything not matched goes via VPN; "false" — direct.
RemoteDNSType · RemoteDNSDomain · RemoteDNSIPDNS via VPN: DoH or DoU (UDP), DoH URL and server IP.
DomesticDNSType · DomesticDNSDomain · DomesticDNSIPDNS for direct connections.
Geoipurl · GeositeurlWhere to download geoip.dat / geosite.dat. Use Rameta's trimmed files (below).
LastUpdatedUnix time of the profile version; a newer value makes devices update it.
DnsHostsStatic records {"domain": "IP"}.
DirectSites · DirectIpDirect.
ProxySites · ProxyIpVia VPN.
BlockSites · BlockIpBlock.
DomainStrategyAsIs · IPIfNonMatch · IPOnDemand
FakeDNS"true"/"false" — FakeDNS for domain rules.
{
  "Name": "Мой VPN — РФ напрямую",
  "GlobalProxy": "true",
  "RemoteDNSType": "DoH",
  "RemoteDNSDomain": "https://cloudflare-dns.com/dns-query",
  "RemoteDNSIP": "1.1.1.1",
  "DomesticDNSType": "DoU",
  "DomesticDNSDomain": "",
  "DomesticDNSIP": "77.88.8.8",
  "Geoipurl": "https://vpn.rameta.net/geo/geoip.dat?tags=ru,private",
  "Geositeurl": "https://vpn.rameta.net/geo/geosite.dat?tags=category-ru,private",
  "LastUpdated": "1761609600",
  "DnsHosts": {},
  "DirectSites": [
    "geosite:category-ru",
    "geosite:private",
    "domain:ru",
    "domain:xn--p1ai"
  ],
  "DirectIp": [
    "geoip:ru",
    "geoip:private"
  ],
  "ProxySites": [
    "domain:youtube.com",
    "keyword:openai"
  ],
  "ProxyIp": [
    "91.108.4.0/22"
  ],
  "BlockSites": [
    "geosite:category-ads"
  ],
  "BlockIp": [],
  "DomainStrategy": "IPIfNonMatch",
  "FakeDNS": "false"
}

Domain rules use Xray syntax: domain: (domain and subdomains), full: (exact), keyword:, regexp:, geosite:. IPs: address, CIDR or geoip:. Order: block → proxy → direct → default (GlobalProxy). Rameta also accepts RouteOrder (default block-proxy-direct).

# make a link from a profile file
echo "rameta://routing/onadd/$(base64 -w0 profile.json)"

Ready-made profiles

The same profiles are served by /v1/presets together with 50+ services and domain zones.

ProfileDescriptionGeo data
Russian sites direct
ru-direct
Everything via VPN except Russian sites and IPs.geosite:category-ru, geosite:private, geosite:ru-available-only-inside, geoip:private, geoip:ruOpen
Russian sites direct + ad blocking
ru-direct-noads
Russian sites direct plus blocking of major ad networks (light list, ~800 domains).geosite:category-ads, geosite:category-ru, geosite:private, geosite:ru-available-only-inside, geoip:private, geoip:ruOpen
Everything via VPN
all-proxy
All traffic via VPN; only the local network goes direct.—Open
Only blocked sites via VPN
blocked-only
Only sites blocked in Russia (and geo-blocked AI services) go via VPN; everything else direct.geosite:category-ai-!cn, geosite:ru-blocked, geoip:ru-blocked-communityOpen
Everything via VPN + ad blocking
ads-block
Everything via VPN with full ad/tracker blocking (geosite:category-ads-all, ~150k domains; adds ~20 MB to the VPN extension memory, iOS limit is 50 MB).geosite:category-ads-allOpen
Only AI services via VPN
ai-only
Only foreign AI services via VPN; everything else direct.geosite:category-ai-!cnOpen

9. Trimmed geo files

The iOS VPN extension has ~50 MB of memory while the full geosite.dat is ~70 MB. So Rameta (and your profile) fetches only the entries it needs:

https://vpn.rameta.net/geo/geosite.dat?tags=category-ru,private,category-ads
https://vpn.rameta.net/geo/geoip.dat?tags=ru,private

Source: runetfreedom/russia-v2ray-rules-dat (updated daily). Useful tags: category-ru, ru-blocked, ru-available-only-inside, category-ads-all, category-gov-ru, category-ai-!cn; geoip: ru, private, ru-blocked, ru-blocked-community, telegram. Full list: /geo/tags.

10. Provider registry

Register so Rameta shows your logo, color, announcement and offers your routing profile. Then send providerid: <your id> in the subscription. The “verified” badge is granted manually after we contact you.

11. Crypt link generator

Turns a subscription URL into rameta://crypt/…: the payload is the binary age ciphertext for the Rameta public key, base64url without =. The URL is neither stored nor logged.

Same via the API, or locally without our server:

curl -s https://vpn.rameta.net/v1/crypt -H 'Content-Type: application/json' \
  -d '{"url":"https://sub.example.com/abcdef"}'
# → {"link":"rameta://crypt/YWdlLWVuY3J5cHRpb24ub3JnL3Yx…","armored":"-----BEGIN AGE ENCRYPTED FILE-----…"}
# Rameta public key: https://vpn.rameta.net/v1/app/config → agePublicKey
URL='https://sub.example.com/abcdef'
printf '%s' "$URL" | age -r age1zaymjnnxuh8xsmrw9cjajm4gqnjsgtxf6e8v54zx3rpejqsluyls0eymfz | basenc --base64url | tr -d '=\n' | sed 's|^|rameta://crypt/|'
Crypt links hide the URL from casual viewing (screenshots, forwarding, link previews) but not from reverse engineering: the decryption key ships in the app. They do not replace real subscription protection (short-lived tokens, HWID limit).

12. API

Base: https://vpn.rameta.net. Responses are JSON; errors: {"error":"…","message":"…"}.

MethodDescription
GET /v1/app/configVersions, ping URL, geo base, presets version, age public key.
GET /v1/presetsServices, domain zones and ready profiles (ETag).
GET /geo/tagsAll geosite and geoip tags.
GET /geo/geosite.dat?tags=…Trimmed geosite.dat (ETag, Last-Modified).
GET /geo/geoip.dat?tags=…Trimmed geoip.dat.
POST /v1/crypt{"url"} → {"link","armored"}; 30 req/min per IP.
POST /v1/providersRegister → {"id","adminToken"}.
GET /v1/providers/{id}Public provider card.
PUT /v1/providers/{id}Update (Authorization: Bearer <adminToken>).
GET /healthz{"ok":true}

Updating your provider card:

curl -X PUT https://vpn.rameta.net/v1/providers/AbC123xY \
  -H 'Authorization: Bearer <adminToken>' -H 'Content-Type: application/json' \
  -d '{"logoUrl":"https://example.com/logo.png","color":"#7C3AED","announce":"Скидка 20% до пятницы",
       "routingProfile":{"id":"my-ru","name":"РФ напрямую","globalProxy":true,
         "direct":["geosite:category-ru","domain:ru"],"directIp":["geoip:ru","geoip:private"],
         "proxy":[],"proxyIp":[],"block":[],"blockIp":[],"domainStrategy":"IPIfNonMatch"}}'

PUT fields: name, website, supportUrl, logoUrl (https), color (#RRGGBB), announce (≤200), routingProfile (object as in /v1/presets, or null), contact, subscriptionDomains. Missing fields are untouched, null clears.