DEV Community

nestt for v2hub

Posted on

v2hub: an ecosystem for developers

In previous posts I talked about v2hub — an ecosystem for managing VPN subscriptions — and about how I ended up building it. Back then the service was simple: a user has a token, and they use that token to manage their own subscriptions. Through v2hub-cli, through v2hub-panel, through the bot — didn't matter which, the point was the same: a user only ever had the subscriptions they created themselves.

That worked well, and it still does. But then people started showing up who wanted to build their own service on top of v2hub, or integrate it into a product they already had. It quickly became clear that the current feature set wasn't enough for that.

Because now there isn't just one owner — there are two: the end user, who wants a working VPN, and the developer, who wants to offer that VPN as part of their own service. And you can't just hand the user's token to the developer — that's the user's personal key, not something you trade around.

Sure, you could pass your own personal token so a third-party service creates subscriptions on your behalf. But that's awkward for everyone involved: for the user (the service could just delete a subscription they still need), and for the developer (they'd have to store a separate token per user).

So v2hub got providers.

What exactly is a provider

A provider is a separate account type that can, with the user's consent, manage that user's subscriptions. A provider doesn't have subscriptions of its own — if it needs any, it just uses a regular user API-Token for that. Technically, a provider token is the same API-Token a regular user has, just flagged differently on the server side. So a provider keeps using the same v2hub library, the same methods, just with one extra parameter: who it's currently acting on behalf of.

If a call used to look like this:

async with AsyncVPNClient(base_url, api_token) as client:
    sub = await client.create_subscription("my-vpn")
Enter fullscreen mode Exit fullscreen mode

Now a provider can do the same thing, but for a specific user:

async with AsyncVPNClient(base_url, provider_api_token) as client:
    sub = await client.create_subscription(
        "welcome-vpn",
        as_provider_for_user_id=98765,
    )
Enter fullscreen mode Exit fullscreen mode

One provider, one token — and it can manage subscriptions for any number of users who've agreed to it.

Consent isn't a formality — it's its own flow

I deliberately didn't make it so a provider could just grab a user_id and start managing that user's subscription right away. That would've been a hole the size of the API itself. Instead, there's a full authorization lifecycle:

  1. The provider requests access to a user.
  2. If the user already exists in the system, a pending request immediately shows up for them — visible through v2hub-cli, the panel, or the bot. The provider, in turn, gets a connection link it can hand to the user directly.
  3. What if the user doesn't exist yet? Just creating a new user from whatever user_id was passed in would be a bad idea — that ID might not correspond to anyone real. So in that case the server returns an HMAC-signed invite link in the form conn_{hmac}_{provider_name}, which the bot wraps into a deep link to itself (t.me/v2hubot?start=...). The user follows the link, the server verifies the signature, and only then, on the user's behalf, confirms both account creation and the connection request.
  4. The user approves or rejects the request.
  5. Only after approval can the provider do anything with subscriptions — and only with the ones it created itself. Subscriptions the user created on their own are off-limits to the provider. Every provider gets its own isolated space to manage subscriptions in, and it can't step outside of it.

There's one detail I want to call out specifically, because it was a deliberate design choice: revoking access is not the same as deleting it. If a user changes their mind and revokes a provider, every subscription that provider created stops being visible to the user, but nothing actually gets deleted — those subscriptions keep existing, the provider just can't touch them anymore. Only the provider itself can fully delete a connection. The one exception: if the provider never created any subscriptions for that user in the first place, the connection is deleted automatically, with no extra steps needed.

The point of this split is simple: a user can disconnect a provider and reconnect later, whenever they want. The provider doesn't have to recreate anything — everything becomes available again the moment the user reconnects.

What about limits

A single user can connect a limited number of providers — five by default. Past that, connecting one more means revoking one of the existing ones first. This exists so users don't end up dragging every provider under the sun into their account, and to cut down on request spam.

This limit doesn't affect a provider's ability to send a connection request, though — the request still gets created and sits in pending, the user just physically can't approve it until they free up a slot.

Why this matters for developers

If you're building your own Telegram bot that sells VPN access, or you want to simplify and improve a product you already have — you no longer need to stand up your own backend for aggregating configs, storing subscriptions, and resolving nested references. That logic already exists in v2hub, and you get access to it as a provider:

  • create a subscription for a user the moment they pay for access;
  • add or remove sources as needed (say, one of your servers goes down — quietly swap the source, and the user won't even notice);
  • hand the user a ready-made public subscription link they paste into their VPN client once and never touch again.

Meanwhile, you don't have to store anything on your end besides a mapping of "our internal client ID → their user_id in v2hub." All the heavy lifting — aggregation, caching, resolution, access control — stays on v2hub's side.

Someone will point out: Marzban and 3x-ui already do this, they can build subscriptions too. True — but only out of what was created inside them. If some of your servers run Marzban, some run 3x-ui, and a couple you set up manually with hand-written configs, there's no way to combine them using those tools as-is — you'd have to bolt together some workaround to tie them all together (the same "extended faucet" problem from my previous post). v2hub removes that problem entirely: build a single subscription out of configs from any servers, any protocols, all at once. You can even keep a separate subscription per server and then merge them all into one through v2hub — that's supported too.

Why this matters for end users

You'd think this is purely a developer-facing feature, but here's the thing: users used to have to remember which VPN came from which service, what that service was even called, and where to go find it. Now every service they use is in one place. They can switch providers daily if they want — all they have to do is open v2hub once, and every one of their subscriptions is right there.

It's a win for the services themselves, too: the easier it is for a user to manage everything through one account, the more likely they are to stick around and come back — instead of giving up and going to deal with yet another separate dashboard somewhere else.


Components, if you want to poke at providers yourself:


P.S. As always, happy to hear any feedback or suggestions.

Top comments (0)