<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: nestt</title>
    <description>The latest articles on DEV Community by nestt (@nestt).</description>
    <link>https://dev.to/nestt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049828%2F91edc234-b2dd-4d75-9cf2-b54ce811ea0c.jpg</url>
      <title>DEV Community: nestt</title>
      <link>https://dev.to/nestt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nestt"/>
    <language>en</language>
    <item>
      <title>v2hub: an ecosystem for developers</title>
      <dc:creator>nestt</dc:creator>
      <pubDate>Thu, 10 Sep 2026 10:56:07 +0000</pubDate>
      <link>https://dev.to/v2hub/v2hub-an-ecosystem-for-developers-5e01</link>
      <guid>https://dev.to/v2hub/v2hub-an-ecosystem-for-developers-5e01</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1"&gt;previous posts&lt;/a&gt; I talked about v2hub — an ecosystem for managing VPN subscriptions — and about &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm"&gt;how I ended up building it&lt;/a&gt;. Back then the service was simple: a user has a token, and they use that token to manage their own subscriptions. Through &lt;a href="https://pypi.org/project/v2hub-cli/" rel="noopener noreferrer"&gt;&lt;code&gt;v2hub-cli&lt;/code&gt;&lt;/a&gt;, through &lt;a href="https://panel.v2hub.link" rel="noopener noreferrer"&gt;&lt;code&gt;v2hub-panel&lt;/code&gt;&lt;/a&gt;, through the &lt;a href="https://t.me/v2hubot" rel="noopener noreferrer"&gt;bot&lt;/a&gt; — didn't matter which, the point was the same: a user only ever had the subscriptions they created themselves.&lt;/p&gt;

&lt;p&gt;That worked well, and it still does. But then people started showing up who wanted to build &lt;strong&gt;their own&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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).&lt;/p&gt;

&lt;p&gt;So v2hub got &lt;strong&gt;providers&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly is a provider
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;API-Token&lt;/code&gt; for that. Technically, a provider token is the same &lt;code&gt;API-Token&lt;/code&gt; a regular user has, just flagged differently on the server side. So a provider keeps using the same &lt;code&gt;v2hub&lt;/code&gt; library, the same methods, just with one extra parameter: who it's currently acting on behalf of.&lt;/p&gt;

&lt;p&gt;If a call used to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncVPNClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-vpn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a provider can do the same thing, but for a specific user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;AsyncVPNClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;provider_api_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sub&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_subscription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;welcome-vpn&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;as_provider_for_user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;98765&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One provider, one token — and it can manage subscriptions for any number of users who've agreed to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent isn't a formality — it's its own flow
&lt;/h2&gt;

&lt;p&gt;I deliberately didn't make it so a provider could just grab a &lt;code&gt;user_id&lt;/code&gt; 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:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The provider requests access to a user.&lt;/li&gt;
&lt;li&gt;If the user already exists in the system, a &lt;code&gt;pending&lt;/code&gt; request immediately shows up for them — visible through &lt;code&gt;v2hub-cli&lt;/code&gt;, the panel, or the bot. The provider, in turn, gets a connection link it can hand to the user directly.&lt;/li&gt;
&lt;li&gt;What if the user doesn't exist yet? Just creating a new user from whatever &lt;code&gt;user_id&lt;/code&gt; 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 &lt;code&gt;conn_{hmac}_{provider_name}&lt;/code&gt;, which the bot wraps into a deep link to itself (&lt;code&gt;t.me/v2hubot?start=...&lt;/code&gt;). 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.&lt;/li&gt;
&lt;li&gt;The user approves or rejects the request.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There's one detail I want to call out specifically, because it was a deliberate design choice: &lt;strong&gt;revoking access is not the same as deleting it&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about limits
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Why this matters for developers
&lt;/h2&gt;

&lt;p&gt;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:&lt;/p&gt;

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

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

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for end users
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Components, if you want to poke at providers yourself:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Core (API):&lt;/em&gt; &lt;a href="https://github.com/nestthub/v2hub-api" rel="noopener noreferrer"&gt;&lt;em&gt;v2hub-api&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Library:&lt;/em&gt; &lt;a href="https://pypi.org/project/v2hub" rel="noopener noreferrer"&gt;&lt;em&gt;v2hub&lt;/em&gt;&lt;/a&gt; &lt;em&gt;on PyPI&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Docs:&lt;/em&gt; &lt;a href="https://v2hub.dev" rel="noopener noreferrer"&gt;&lt;em&gt;v2hub.dev&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Panel:&lt;/em&gt; &lt;a href="https://panel.v2hub.link" rel="noopener noreferrer"&gt;&lt;em&gt;panel.v2hub.link&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Bot:&lt;/em&gt; &lt;a href="https://t.me/v2hubot" rel="noopener noreferrer"&gt;&lt;em&gt;t.me/v2hubot&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;P.S. As always, happy to hear any feedback or suggestions.&lt;/p&gt;

</description>
      <category>v2hub</category>
      <category>vpn</category>
      <category>api</category>
      <category>opensource</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>nestt</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:12:11 +0000</pubDate>
      <link>https://dev.to/nestt/-2m2a</link>
      <guid>https://dev.to/nestt/-2m2a</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm" class="crayons-story__hidden-navigation-link"&gt;How I Ended Up Building V2Hub&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/v2hub"&gt;
            &lt;img alt="v2hub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14161%2F6f030cad-6f32-4af7-9fd1-5c24a2ad12a3.jpg" class="crayons-logo__image" width="799" height="455"&gt;
          &lt;/a&gt;

          &lt;a href="/nestt" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049828%2F91edc234-b2dd-4d75-9cf2-b54ce811ea0c.jpg" alt="nestt profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nestt" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nestt
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nestt
                
              
              &lt;div id="story-author-preview-content-4250745" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nestt" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049828%2F91edc234-b2dd-4d75-9cf2-b54ce811ea0c.jpg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nestt&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/v2hub" class="crayons-story__secondary fw-medium"&gt;v2hub&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 28&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm" id="article-link-4250745"&gt;
          How I Ended Up Building V2Hub
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/fastapi"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;fastapi&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vpn"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vpn&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How I Ended Up Building V2Hub</title>
      <dc:creator>nestt</dc:creator>
      <pubDate>Tue, 28 Jul 2026 06:11:56 +0000</pubDate>
      <link>https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm</link>
      <guid>https://dev.to/v2hub/how-i-ended-up-building-v2hub-3ehm</guid>
      <description>&lt;p&gt;Like everyone else, I started out using popular tools like Marzban and 3x-ui. And to be fair, they're genuinely convenient — as long as you want to create a config &lt;em&gt;inside&lt;/em&gt; that tool and add it to a subscription. But here's the catch: what if you want to add a config to your subscription that wasn't created in Marzban or 3x-ui in the first place? Even if you spin up two independent servers running separate but identical Marzban instances, you can't just merge them. You end up hacking together some workaround to glue them together.&lt;/p&gt;

&lt;p&gt;It's a bit like British bathroom taps: hot on one side, cold on the other, and you have to rig up some extension just to mix them and actually be able to wash your hands.&lt;/p&gt;

&lt;p&gt;At some point I gave up on that whole approach and started manually setting up xray and writing configs myself. The problem is, if an IP got blocked and it was easier to just spin up a new VPS, I'd have to manually re-add all the configs into every app all over again. Tolerable when it's just you. Considerably less tolerable once you start sharing configs with friends — especially the older, less tech-savvy ones.&lt;/p&gt;

&lt;p&gt;So I wrote a scrappy little script where I'd manually punch in all the configs, and it would spit out a subscription link — one I could just send to end users. And that's when it hit me: how many other people out there are dealing with the exact same headache? Not just managing configs, but managing &lt;em&gt;subscriptions&lt;/em&gt; on top of that.&lt;/p&gt;

&lt;p&gt;Originally I wanted to build a simple website where users could assemble their own subscription from a bunch of configs. But things got a little out of hand along the way — and what I ended up with instead was a standalone FastAPI service where you can build subscriptions out of individual configs &lt;em&gt;and&lt;/em&gt; out of other subscriptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What good is an API without a nice client?
&lt;/h2&gt;

&lt;p&gt;But what's an API without a comfortable library to actually use it? And if a user isn't going to self-host the whole API, why would they need admin tooling bundled in? So I split it into two libraries: &lt;strong&gt;v2hub&lt;/strong&gt; and &lt;strong&gt;v2hub-admin&lt;/strong&gt;. Both ship sync and async clients, pick your poison.&lt;/p&gt;

&lt;p&gt;And what about people who want to use the service but don't want to touch any code at all? That's where the &lt;strong&gt;CLI&lt;/strong&gt; comes in — built on Typer and Rich, so it actually looks nice while you're at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auth without the pain
&lt;/h2&gt;

&lt;p&gt;How do you keep spam and throwaway accounts under control without forcing users through some invasive sign-up flow — email verification, phone number, ID scan, the works? Enter Telegram. I built a bot that hands users a token they can use to access everything, and lets them reset that token whenever they want without breaking anything — because subscriptions are tied to an internal &lt;code&gt;user_hash&lt;/code&gt;, not the token itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  And then it grew a bit too much
&lt;/h2&gt;

&lt;p&gt;Eventually the service grew to the point where it wasn't just useful for developers anymore — regular people wanted in too. Which surfaced a new problem: regular people don't really care for CLIs, client libraries, or, god forbid, raw curl requests against an API.&lt;/p&gt;

&lt;p&gt;So the natural next step was a proper web panel — something people could just click through, no terminal required. That's how &lt;strong&gt;v2hub-panel&lt;/strong&gt; came to be.&lt;/p&gt;

&lt;p&gt;That's roughly how the V2Hub story started. Let's see where it goes from here.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you want to poke around:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Core API: &lt;a href="https://github.com/nestthub/v2hub-api" rel="noopener noreferrer"&gt;v2hub-api&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Web panel: &lt;a href="https://panel.v2hub.link" rel="noopener noreferrer"&gt;panel.v2hub.link&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Telegram bot: &lt;a href="https://t.me/v2hubot" rel="noopener noreferrer"&gt;t.me/v2hubot&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Libraries on PyPI: &lt;a href="https://pypi.org/project/v2hub" rel="noopener noreferrer"&gt;v2hub&lt;/a&gt; and &lt;a href="https://pypi.org/project/v2hub-admin" rel="noopener noreferrer"&gt;v2hub-admin&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;CLI: &lt;a href="https://pypi.org/project/v2hub-cli" rel="noopener noreferrer"&gt;v2hub-cli&lt;/a&gt;&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>fastapi</category>
      <category>vpn</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>nestt</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:52:13 +0000</pubDate>
      <link>https://dev.to/nestt/-een</link>
      <guid>https://dev.to/nestt/-een</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1" class="crayons-story__hidden-navigation-link"&gt;V2Hub — An Ecosystem for Managing VPN Subscriptions (V2Ray/Xray/VLESS/VMess/Trojan/Hysteria2)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/v2hub"&gt;
            &lt;img alt="v2hub logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14161%2F6f030cad-6f32-4af7-9fd1-5c24a2ad12a3.jpg" class="crayons-logo__image" width="799" height="455"&gt;
          &lt;/a&gt;

          &lt;a href="/nestt" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049828%2F91edc234-b2dd-4d75-9cf2-b54ce811ea0c.jpg" alt="nestt profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/nestt" class="crayons-story__secondary fw-medium m:hidden"&gt;
              nestt
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                nestt
                
              
              &lt;div id="story-author-preview-content-4246752" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/nestt" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049828%2F91edc234-b2dd-4d75-9cf2-b54ce811ea0c.jpg" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;nestt&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/v2hub" class="crayons-story__secondary fw-medium"&gt;v2hub&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 27&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1" id="article-link-4246752"&gt;
          V2Hub — An Ecosystem for Managing VPN Subscriptions (V2Ray/Xray/VLESS/VMess/Trojan/Hysteria2)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/opensource"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;opensource&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/python"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;python&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/vpn"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;vpn&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/fastapi"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;fastapi&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>V2Hub — An Ecosystem for Managing VPN Subscriptions (V2Ray/Xray/VLESS/VMess/Trojan/Hysteria2)</title>
      <dc:creator>nestt</dc:creator>
      <pubDate>Mon, 27 Jul 2026 16:21:05 +0000</pubDate>
      <link>https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1</link>
      <guid>https://dev.to/v2hub/v2hub-an-ecosystem-for-managing-vpn-subscriptions-v2rayxrayvlessvmesstrojanhysteria2-ln1</guid>
      <description>&lt;h2&gt;
  
  
  Why this exists
&lt;/h2&gt;

&lt;p&gt;If you've ever handed out VPN configs or small subscription links to friends or clients manually, you know the pain: one provider goes down, another changes its address, a third needs to be temporarily hidden — and the end user is supposed to keep using a single, unchanging subscription URL that always resolves to the current list of servers. V2Hub is an attempt to solve this end-to-end: not just a proxy aggregator, but a full ecosystem — API, web panel, CLI, Telegram bot, and admin tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One clarification up front: this project does not generate VPN configs. If you're looking for how to set up a VPN server from scratch, check out vless.wiki. V2Hub is purely about managing subscriptions and configs you already have — aggregating them, hiding sources, annotating them, resolving nested references, and serving it all as one link.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The components
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Try it live&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-api&lt;/td&gt;
&lt;td&gt;&lt;a href="https://v2hub.link" rel="noopener noreferrer"&gt;v2hub.link&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Core — FastAPI service that aggregates and resolves subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-core&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pypi.org/project/v2hub/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python library with core methods for managing subscriptions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-admin&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pypi.org/project/v2hub-admin/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Add-on module providing admin functionality (HMAC auth, user CRUD)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-cli&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pypi.org/project/v2hub-cli/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Terminal client built with Typer and Rich&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-panel&lt;/td&gt;
&lt;td&gt;&lt;a href="https://panel.v2hub.link" rel="noopener noreferrer"&gt;panel.v2hub.link&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Web panel for managing subscriptions (FastAPI + vanilla JS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v2hub-bot&lt;/td&gt;
&lt;td&gt;&lt;a href="https://t.me/v2hubot" rel="noopener noreferrer"&gt;Telegram Bot&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Telegram bot for self-service subscription access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The core — v2hub-api
&lt;/h2&gt;

&lt;p&gt;This is the most fleshed-out piece, so it's worth going into some detail.&lt;/p&gt;

&lt;p&gt;Multi-source aggregation. A subscription can pull configs from three kinds of sources at once: raw URIs (vless://, vmess://, trojan://, ss://, etc.), external subscription URLs from another provider, and internal references to other subscriptions inside the same system.&lt;/p&gt;

&lt;p&gt;Recursive resolution. If subscription A references subscription B, which references C, everything gets flattened into a single list by the time it reaches the client. There's circular-reference detection built in, plus a configurable nesting depth limit (max_depth, default 3).&lt;/p&gt;

&lt;p&gt;Per-source fine-tuning. Any source inside a subscription can be hidden from the resolved output (is_hidden) without deleting it, and annotated with its own comment.&lt;/p&gt;

&lt;p&gt;Two-tier caching. Redis as a fast L1 cache, PostgreSQL as the source of truth. A Celery worker refreshes external sources on a schedule (Celery beat, every 15 minutes), so resolution is almost always served from cache. Next up: replacing the periodic worker with lazy refresh, triggered right at request time instead.&lt;/p&gt;

&lt;p&gt;Admin-side security. Admin endpoints aren't protected by a plain token — they require an HMAC-SHA256 signature over the request (timestamp + method + path + body), plus an IP whitelist on top. There's also Redis-backed rate limiting per endpoint, with automatic IP bans once violations pile up.&lt;/p&gt;

&lt;p&gt;Core stack: FastAPI 0.136, SQLAlchemy 2.0 (async) + asyncpg, Pydantic 2.13, Celery 5.6, Alembic for migrations, Uvicorn + Nginx.&lt;/p&gt;

&lt;h2&gt;
  
  
  Panel, CLI, bot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v2hub-panel&lt;/strong&gt; is a minimal web UI for managing subscriptions and sources straight from the browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2hub-cli&lt;/strong&gt; is built on Typer and Rich — you can create subscriptions, add sources, and run admin operations right from the terminal with nicely formatted output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2hub-bot&lt;/strong&gt; is a simple bot whose main job is issuing access tokens. It embeds v2hub-panel as a mini-app for extra convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python libraries on PyPI
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;v2hub&lt;/strong&gt; — a library that bundles everything an end user needs to work with subscriptions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v2hub-admin&lt;/strong&gt; — an add-on to the core library that handles user CRUD, plus allow-lists and ban-lists.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this might be useful for
&lt;/h2&gt;

&lt;p&gt;Anyone running a small VPN service for themselves, friends, or a small company, who wants a ready-made infrastructure with monitoring built in instead of hacking something together.&lt;br&gt;
Anyone curious what a genuinely "production-ready" FastAPI project looks like end to end — migrations, async ORM, HMAC auth, observability, CI/CD — regardless of the domain (VPN is more the excuse here than the point).&lt;br&gt;
Developers who want to see a working example of recursive resolution over nested entities with cycle protection — a pattern that's useful well beyond VPN tooling.&lt;br&gt;
Where this is headed&lt;/p&gt;

&lt;p&gt;If the project finds an audience and builds up a small community around it, we'll keep developing it. The long-term goal is for V2Hub to become a real alternative to Marzban API and 3x-ui for subscription management — something usable not just by hobbyists and small projects, but by full-fledged VPN companies too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ecosystem overview: &lt;a href="https://github.com/nestthub/nestthub/blob/main/ecosystems/v2hub/README.md" rel="noopener noreferrer"&gt;nestthub/nestthub — ecosystems/v2hub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Core (API): &lt;a href="https://github.com/nestthub/v2hub-api" rel="noopener noreferrer"&gt;nestthub/v2hub-api&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Shared logic: &lt;a href="https://github.com/nestthub/v2hub-core" rel="noopener noreferrer"&gt;nestthub/v2hub-core&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Web panel: &lt;a href="https://github.com/nestthub/v2hub-panel" rel="noopener noreferrer"&gt;nestthub/v2hub-panel&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CLI: &lt;a href="https://github.com/nestthub/v2hub-cli" rel="noopener noreferrer"&gt;nestthub/v2hub-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Admin module: &lt;a href="https://github.com/nestthub/v2hub-admin" rel="noopener noreferrer"&gt;nestthub/v2hub-admin&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Telegram bot: &lt;a href="https://github.com/nestthub/v2hub-bot" rel="noopener noreferrer"&gt;nestthub/v2hub-bot&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Licenses vary by repo (the API is Apache-2.0, the panel is GPL-3.0), so check the specific repo before using this commercially.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>v2hub</category>
      <category>api</category>
      <category>opensource</category>
      <category>vpn</category>
    </item>
  </channel>
</rss>
