internal/commerce, because a customer
who is told to install one application in the chat and a different one in the
browser has been handed two products.
The subscription link
What it is. A URL minted and owned by Remnawave for one subscription. A client application fetches it, receives the configuration that subscription grants, and connects. Why Omniflow does not keep a copy. The link is a credential, and the cheapest way to guarantee that a credential never appears in a backup, a log line, or a crash report is never to hold it. Omniflow reads it from Remnawave at the moment a customer asks for it and passes it straight to the screen. The consequences are visible throughout the product:GET /v1/account/overview— the dashboard payload — deliberately carries no subscription URL. A dashboard response that always carried it would put it in every cache, log, and browser-history entry that touches the page.- Every
/v1/accountresponse is sent withCache-Control: no-store,Referrer-Policy: no-referrer, and aContent-Security-Policyofdefault-src 'none'. The comment on that middleware names the reason: a customer response can contain a subscription link. - The personal-data export declares
subscription_linksamong its standing redactions rather than including them. See Privacy and your data. - The customer’s own security log records a rotation by the subscription’s label and nothing else — putting the link in the log would defeat the rotation it is recording.
409 not_provisioned and shows “Still being set up” rather than an empty box.
See Fulfillment for what is happening during that
window.
Getting the link onto a device
From the web panel
1
Open the subscription
On
/account, press Connect on the subscription’s card. That leads to
/account/subscriptions/{id}/connect. The screen also accepts a ?platform=
query, so a link into it can preselect a platform.2
Pick a platform
The picker offers iOS, Android, Windows, macOS and Linux, in that fixed
order. An unknown or empty platform falls back to the first one rather than
erroring.
3
Follow the three steps
Install the first recommended application for that platform; press the button
that adds the subscription; if nothing opens, copy the link and paste it into
the application. The steps name the actual application rather than “your VPN
app”, because the customer has to find it in a store.
4
Hand the link over
Press the Open in … button for the client you installed, point a second
device’s camera at
the QR code, or press Copy and paste the link manually. All three carry
the same string.
From the Telegram bot
The subscription screen has a Connect button. It opens the same platform picker, and each platform shows the same numbered steps naming the same first recommended application. The difference is the buttons. Telegram inline buttons accept onlyhttp,
https and tg URLs, so an application deep link such as happ://add/… cannot
be a link button at all. The bot therefore renders one copy-text button per
client, plus a copy button for the bare link, plus an “Open subscription” link
button for the https subscription URL itself. Copying is the documented
fallback everywhere, including on a desktop browser that has no handler
registered for the scheme.
Every bot screen is sent as protected content, so Telegram will not let a message
carrying a subscription link be forwarded. The full Telegram flow is described in
the Telegram bot.
The clients an installation documents
The catalogue is stored rather than compiled in, and both surfaces read the same rows — so the bot and the browser cannot recommend different applications. It is edited at/admin/settings/connect. Order is
recommendation order: the first entry is the application the setup steps name.
A fresh installation is seeded with exactly what earlier versions had built in:
A deep link is the scheme with the subscription URL appended, and nothing else —
happ://add/https://panel.example.com/sub/…. There is no redirect service in
between, so a client that supports its scheme imports the subscription without
any part of Omniflow observing that it happened.
An operator may also give a client a download address and write their own setup
instructions per platform and per language. Instructions replace the generic
three steps rather than being added to them: somebody who described their own
setup knows something the generic copy does not, and showing both would leave a
customer choosing between two accounts of the same thing.
The seeded list is deliberately short. These are the clients the installation
documents and supports, not every client that can read a subscription link — a
customer using something else can still paste the link into it, but the steps
on screen will not describe their application.
The three ways the link travels
Deep links
In the browser each client is an ordinary anchor rather than a router navigation, because a deep link leaves the page for an application. On a device with the application installed, pressing it opens the client with the subscription already filled in. On a device without it, the browser reports that it cannot open the address — which is why the third step tells the customer to copy the link instead, and why the bot offers the deep link as copyable text.The QR code
The QR is generated in the browser. The encoder is imported lazily so it is not in the bundle of every other screen, and the result is rendered from a data URL in a plain<img> element.
It is worth stating why it is not a server-rendered image: an image URL carrying
a subscription link would put that credential into a request line, a proxy log,
and the browser cache, and routing it through Next.js’s image pipeline would send
a credential-bearing payload to the server to be cached. If the encoder fails,
the QR panel is omitted entirely rather than rendered broken.
Point another device’s camera at the code and it receives the same string the
deep link would have carried.
Copying it by hand
The link is shown truncated in a monospace row with a Copy button. It is truncated because it is a credential and the customer has no reason to read it, only to move it. The button shows “Copied” for about two seconds and then reverts. A browser that refuses clipboard access is a real case — it happens on insecure origins and in some in-app browsers — so a failed copy raises a toast telling the customer to select and copy the link manually, rather than doing nothing and looking like a broken button.When the connect screen shows no link
In the bot, a subscription with no link yet shows the line “Your subscription
link appears once access is active” in place of the buttons.
API.
GET /v1/account/subscriptions/{subscriptionID}/connection?platform=
returns {subscriptionUrl, platform, platforms[], clients[{name, deepLink}]}.
Connected devices
What it is. The devices currently attached to one subscription’s Remnawave user, with a count against the plan’s device limit, a remove control per device, and a remove-all control. Why it exists. A device limit is only enforceable if the customer can free a slot themselves. Without a device list, hitting the limit means opening a support ticket to disconnect a phone that was replaced a year ago. How to reach it. Devices are not on the tab bar — replacing a phone is a once-a-year action, and the five tabs are ordered by frequency of need. Open/account/profile and follow Devices, or reach /account/devices
directly. Devices belong to a subscription rather than to an account, so when a
customer holds more than one the screen shows buttons above the list to switch
between them.
Each row shows an icon chosen from the platform the client reported (a laptop for
mac, Windows or Linux, a tablet for a pad, a phone otherwise), the device model
the client reported about itself — or “Unknown device” when it reported nothing —
and a date. Below the list: “One link works on every device. Disconnecting frees
the slot immediately.”
What is deliberately not shown
There is no hardware identifier and no IP address anywhere on this screen or in the response behind it. That is a decision, not an omission:- The HWID never leaves the server. It identifies somebody’s physical
machine. A device is named to the customer by a handle —
base64urlof the first 16 bytes ofSHA-256("omniflow.device." + hwid), 128 bits — which reveals nothing about the device it stands for. - The handle is resolved by re-hashing, not by a stored mapping. When a removal arrives, the server fetches the current device list, hashes each HWID it holds, and matches. The caller therefore never supplies the identifier being acted on. An array index would not do: the list can change between the read and the removal, so an index can point at a different device by the time it is used.
- Last seen is a date, not an instant. Enough for a customer to recognise their own laptop, not enough to build a movement log out of.
- No IP address is returned at all, including for a household or a connection
somebody else shares. The personal-data export declares
device_and_network_identifiersas a standing redaction for the same reason.
404 not_found — which is also exactly
what a device removed from another tab a moment ago looks like.
Removing devices
1
Remove one device
Press Disconnect on its row. A confirmation dialog explains that the
device stops connecting until the subscription is added again. On success a
toast confirms it and the list reloads. The slot is free immediately.
2
Remove every device
When more than one device is listed, a full-width Disconnect all devices
button appears with its own dialog. It is one Remnawave call rather than a
loop, because doing it device by device would leave a partially disconnected
subscription behind if the third call failed.
401 reauthentication_required, and the panel
shows “Sign in again to confirm this action.” rather than a raw refusal — the
remedy is something the customer can actually do. Removing a single device has no
such requirement: it costs one device a reconnection, not the whole setup.
Remove-all without
?confirm=true is refused with 422 confirmation_required.
With zero devices connected it returns {"removed": 0} without calling Remnawave
at all.
Both removals write into the customer’s own security log — device_removed with
the subscription label and the device’s reported name, devices_removed_all with
the label and the number removed. Neither records a hardware identifier, and the
log is readable by the customer on /account/security.
An operator can also list and remove devices from a customer’s profile in the
admin panel, under
subscriptions.read and subscriptions.write. That path
writes an audit event carrying its own device reference, never the HWID. See
Admin operations.Rotating the subscription link
What it is. Revoking the current access link and reading the new one back from Remnawave. Why it exists. It is the only remedy when a link has been shared or leaked. It is also the most destructive thing a customer can do to their own working setup: every device stops connecting until the new link is imported. Both halves of that sentence are why the control exists and why it is guarded the way it is. How to use it. Open/account/subscriptions/{id} and scroll to the Access
link section. A warning card states exactly what breaks. Press Issue a new
link; a dialog requires the customer to type the phrase NEW LINK as well as
press confirm. On success a toast confirms it and a hint points back at the
connect screen to add the new link to each device.
In Telegram the same action sits on the subscription screen behind a
confirm/cancel keyboard, and performing it returns the connect screen with the
fresh link already on it.
API. POST /v1/account/subscriptions/{subscriptionID}/rotate-link, body
{"confirm": true}, behind a recent sign-in. Responds {"subscriptionUrl": "…"}.
The new link is read back from Remnawave rather than assumed: Remnawave mints
it, and showing the customer the old one after a rotation would be worse than
showing none. A
subscription_key_rotated entry is written to the customer’s
security log naming only the subscription’s label.
Related
Customer web panel
The rest of the browser product: dashboard, checkout, wallet, support.
Telegram bot
The same connection flow in chat, with copy-text buttons.
Subscriptions
Entitlements, device limits, expiry, and what a phase means.
Remnawave integration
Which panel owns the link, the devices, and the traffic counters.