<?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: yooexo</title>
    <description>The latest articles on DEV Community by yooexo (@yooexo).</description>
    <link>https://dev.to/yooexo</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%2F4082093%2Ffbf9fbef-19ee-4cb6-808a-30de8bc0e82d.jpg</url>
      <title>DEV Community: yooexo</title>
      <link>https://dev.to/yooexo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yooexo"/>
    <language>en</language>
    <item>
      <title>BigCommerce Multi-Storefront vs Separate Stores: Session Isolation With BitBrowser MCP</title>
      <dc:creator>yooexo</dc:creator>
      <pubDate>Mon, 26 Oct 2026 12:58:00 +0000</pubDate>
      <link>https://dev.to/yooexo/bigcommerce-multi-storefront-vs-separate-stores-session-isolation-with-bitbrowser-mcp-23bj</link>
      <guid>https://dev.to/yooexo/bigcommerce-multi-storefront-vs-separate-stores-session-isolation-with-bitbrowser-mcp-23bj</guid>
      <description>&lt;p&gt;BigCommerce Multi-Storefront (MSF) runs several storefronts from one store: one control panel, one catalog, one list of staff users, and a separate domain for each storefront. Separate stores are the opposite arrangement. Each one is its own subscription with its own store hash, API credentials, staff list and apps.&lt;/p&gt;

&lt;p&gt;Both setups are common, and both end up with the same practical problem for developers, QA people and agencies. You need to be signed in to several things at once (an admin, a test shopper on the German storefront, a different test shopper on the US one) without one session overwriting another or leaking into the next test.&lt;/p&gt;

&lt;p&gt;This post maps where sessions live in each setup, works out how many isolated browser identities each one needs, and then builds and audits those identities with &lt;a href="https://www.bitbrowser.net/?code=forumonly" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; and its MCP server, so an AI client can create, open and organise them from plain prompts. There are two short Python scripts along the way: one reads your storefronts from the BigCommerce API, the other checks every profile for cookies that belong to a different store.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fxa1fmf7ioysq2o2fvp02.png" class="article-body-image-wrapper"&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%2Farticles%2Fxa1fmf7ioysq2o2fvp02.png" alt="Diagram comparing one BigCommerce store with several storefronts and several separate stores, with the sessions each one needs" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Table of contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Two ways to run several storefronts&lt;/li&gt;
&lt;li&gt;What Multi-Storefront shares and what it splits&lt;/li&gt;
&lt;li&gt;What separate stores give you&lt;/li&gt;
&lt;li&gt;Where sessions collide&lt;/li&gt;
&lt;li&gt;How many browser profiles each setup needs&lt;/li&gt;
&lt;li&gt;Connect BitBrowser MCP&lt;/li&gt;
&lt;li&gt;Pull the storefront list from the API&lt;/li&gt;
&lt;li&gt;Build the profiles with prompts&lt;/li&gt;
&lt;li&gt;Audit every profile for cookie bleed&lt;/li&gt;
&lt;li&gt;Test the customer login rules&lt;/li&gt;
&lt;li&gt;Mobile checks on cloud phones&lt;/li&gt;
&lt;li&gt;Multi-Storefront or separate stores&lt;/li&gt;
&lt;li&gt;FAQ&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Two ways to run several storefronts
&lt;/h2&gt;

&lt;p&gt;BigCommerce launched Multi-Storefront on 29 March 2022. Before that, a merchant with two brands or three countries usually ran two or three stores and logged in to each one separately.&lt;/p&gt;

&lt;p&gt;With MSF, every extra storefront is a channel inside the same store. You add them in Channel Manager by buying storefront seats, and each seat gives you one active and one inactive storefront. The default storefront counts toward that. Only the store owner can buy seats, and the number you can buy is capped by your subscription plan, so check your plan before you design around ten storefronts.&lt;/p&gt;

&lt;p&gt;Separate stores need nothing special. A store owner can open several stores on one account from the Account Dashboard, all billed to the same details, and the dashboard signs in with the same credentials as the stores. Each store still runs as its own instance.&lt;/p&gt;

&lt;p&gt;Under the hood the difference shows up in the API. An MSF store has one store hash and many channels: channel &lt;code&gt;1&lt;/code&gt; is the default storefront and can't be deleted, and every Stencil storefront has a site with its own public URL. Separate stores have one store hash each, and every integration has to be set up once per store.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Multi-Storefront shares and what it splits
&lt;/h2&gt;

&lt;p&gt;BigCommerce's own comparison of single and multi-storefront setups sorts settings into three groups.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Examples&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Always per storefront&lt;/td&gt;
&lt;td&gt;domain name, storefront status, the message shown when a storefront is inactive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global by default, can be overridden per storefront&lt;/td&gt;
&lt;td&gt;currencies, checkout, SEO, search engine robots, logo and favicon, customer privacy, order notifications, abandoned cart emails&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed per storefront in Channel Manager&lt;/td&gt;
&lt;td&gt;themes, script manager, web pages, social links, home page carousel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Products are shared, but a product has to be assigned to a channel to appear on that storefront. Shipping methods get a Storefronts tab so you can switch each method on or off per storefront. There are limits too, for example up to 25,000 products per storefront.&lt;/p&gt;

&lt;p&gt;Two shared things matter most for session isolation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staff permissions are global.&lt;/strong&gt; BigCommerce says user permissions are shared across all storefronts, and you can't set them for one storefront. A staff member who can edit orders can edit orders from every storefront in the store.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer accounts can be global or channel-specific.&lt;/strong&gt; Every customer record has a &lt;code&gt;channel_ids&lt;/code&gt; field. When it is &lt;code&gt;null&lt;/code&gt;, the customer can sign in to any channel whose &lt;code&gt;allow_global_logins&lt;/code&gt; setting is &lt;code&gt;true&lt;/code&gt;. When it holds a list such as &lt;code&gt;[2]&lt;/code&gt;, the customer can sign in only to those channels, whatever the global setting says. New customers start as &lt;code&gt;null&lt;/code&gt;, and the default storefront allows global logins out of the box. So in a fresh MSF store, one shopper account works on every storefront unless you change it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What separate stores give you
&lt;/h2&gt;

&lt;p&gt;Separate stores give you hard walls. Each store has its own staff list, so a contractor who works on your outlet store doesn't see the main brand's orders. Each store has its own API accounts, so a leaked token for one brand can't touch the other. Apps are installed per store, which is more setup work but also means one app's bug stays in one place.&lt;/p&gt;

&lt;p&gt;You pay for those walls with repetition. Product data has to be kept in sync between stores by an integration or by hand. Reporting across brands means pulling from several APIs. Every theme change, every script and every app subscription happens once per store.&lt;/p&gt;

&lt;p&gt;For session purposes, though, separate stores are simpler to reason about. Nothing is shared, so the only question is how many logins you need open at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where sessions collide
&lt;/h2&gt;

&lt;p&gt;There are three kinds of session in play, and they collide in different ways.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The admin session.&lt;/strong&gt; In MSF there is one control panel, so one staff login covers every storefront. That is convenient for the merchant. It is a problem for an agency that wants a contractor to work on one storefront only, because global permissions make that impossible inside one store. With separate stores, the merchant can issue a different staff user per store, and an agency often ends up holding several BigCommerce logins of its own: its own user, plus users that clients created for it. A browser keeps one BigCommerce login at a time, so moving between them means signing out and in again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The shopper session.&lt;/strong&gt; A Stencil storefront tracks the shopper with a session cookie (&lt;code&gt;SHOP_SESSION_TOKEN&lt;/code&gt;) on that storefront's domain. Catalyst storefronts keep the session in an Auth.js cookie instead, starting with &lt;code&gt;authjs.anonymous-session-token&lt;/code&gt; before sign-in. Because each MSF storefront has its own domain, two storefronts in the same browser don't share a cart. They do share everything else in that browser, though: the same IP address, the same language headers, the same leftover cookies from last week's test.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The test identity.&lt;/strong&gt; This is where single-browser testing falls apart. Say the US storefront also sells to Canada, and its Canadian dollar has Canada in the "Default to [currency] in these countries" list. BigCommerce doesn't spell out how it decides where a visitor is, but location usually comes from the IP address, so the reliable way to check that default is to browse through a Canadian proxy. To test that a channel-specific customer is refused on the US storefront, you need a clean session with no earlier login on that domain. Doing both in one browser means clearing cookies between every step and remembering which VPN location you left switched on.&lt;/p&gt;

&lt;p&gt;Incognito windows help for one test. They don't help when you want to keep a German test shopper signed in all week while you work on the US checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many browser profiles each setup needs
&lt;/h2&gt;

&lt;p&gt;Here is a practical count. A "profile" means one isolated browser identity: its own cookies, local storage and proxy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;Profiles you need&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MSF store, internal team&lt;/td&gt;
&lt;td&gt;1 admin profile, plus 1 test profile per storefront and customer type&lt;/td&gt;
&lt;td&gt;one admin login covers everything; test shoppers need clean, located sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate stores, internal team&lt;/td&gt;
&lt;td&gt;1 profile per store&lt;/td&gt;
&lt;td&gt;each profile holds that store's admin and its storefront&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agency with client-issued logins&lt;/td&gt;
&lt;td&gt;1 profile per client user&lt;/td&gt;
&lt;td&gt;keeps each client's credentials and cookies apart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Take an MSF store with two storefronts (US and Germany), two customer types (global and channel-specific) and one extra check of the US storefront as seen from Canada. That is one admin profile plus five test profiles. For a merchant with three separate stores, it is three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect BitBrowser MCP
&lt;/h2&gt;

&lt;p&gt;The profile count gets annoying to manage by hand around the tenth profile. That is the job the MCP server takes over.&lt;/p&gt;

&lt;p&gt;BitBrowser is the tool this guide uses because each profile already carries its own cookies, storage, fingerprint and proxy, and because from version 7.1.5 the desktop app ships an MCP server. Its tool groups cover profiles (create, update, open, close, delete, query), fingerprints, proxies (configure, update, check status), groups, tags, window positions and cloud phones. None of those tools act inside a web page: the agent manages the profiles, and you or your test scripts do the clicking.&lt;/p&gt;

&lt;p&gt;To connect it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open Settings, then Browser Settings, then Local API.&lt;/li&gt;
&lt;li&gt;Switch on Enable Authentication Control and copy the API token.&lt;/li&gt;
&lt;li&gt;Add the server to your MCP client.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http bitbrowser http://127.0.0.1:54345/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"x-api-key: YOUR_API_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor, Codex and OpenCode take the same address and header in their own config files. The server listens on your own machine. Keep the token out of prompts, screenshots and repositories, as BitBrowser's guide asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pull the storefront list from the API
&lt;/h2&gt;

&lt;p&gt;Typing storefront URLs into prompts invites typos. The Channels and Sites APIs already know them, so read them from there. You need an API account with read-only access to channels and sites.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List the storefront channels and site URLs of one BigCommerce store.

Prints a starter profiles.json: one entry per storefront, with profile_id left
empty so you can fill it in after BitBrowser MCP creates the profiles.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;STORE_HASH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BC_STORE_HASH&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BC_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;API_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BC_API_ROOT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.bigcommerce.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_ROOT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/stores/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;STORE_HASH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/v3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Auth-Token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_all&lt;/span&gt;&lt;span class="p"&gt;(&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;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read every page of a v3 collection.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&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;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
        &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pagination&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;next&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nxt&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&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;channels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;get_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/channels&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;storefront&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;sites&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/sites&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;site&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sites&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;STORE_HASH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-ch&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;channel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profile_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it once per store hash. For an MSF store you get one row per storefront. For separate stores you get one file per store, which you can merge. Marketplace and POS channels are skipped because they have no site to browse.&lt;/p&gt;

&lt;p&gt;Add the admin host to each row by hand if that profile will also hold the control panel login (for example &lt;code&gt;store-abc123.mybigcommerce.com&lt;/code&gt;). The audit script later uses the &lt;code&gt;hosts&lt;/code&gt; list to decide which cookies belong where. If your admins also preview storefronts from the admin profile, list those storefront hosts in the admin entry too, or the audit will report them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the profiles with prompts
&lt;/h2&gt;

&lt;p&gt;Start the session with a ground rule, then build. These prompts are for the Acme example above: an MSF store with a US and a German storefront.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before you create, update or delete anything, show me the plan and wait for me to type "go".

Create a group named BC-Acme.

In BC-Acme, create these profiles, each with its own fingerprint:
acme-admin, acme-us-global, acme-us-channel, acme-us-from-ca, acme-de-global, acme-de-channel.

Create tags: bc-admin, bc-test, geo-us, geo-de, geo-ca.

Tag acme-admin with bc-admin and the other five with bc-test. Add geo-de to the two acme-de profiles, geo-ca to acme-us-from-ca and geo-us to acme-us-global and acme-us-channel.

Set a German proxy on the geo-de profiles, a Canadian proxy on the geo-ca profile and a US proxy on the geo-us profiles. Leave every other setting unchanged. Then check the proxy status of all five test profiles.

List every profile in BC-Acme as JSON with id, name and tags.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The "leave every other setting unchanged" line comes from BitBrowser's own advice for partial updates. Without it, an agent may reset fields you didn't mention. If a change doesn't show up in the app, refresh and ask the agent to query the profile again before repeating the command.&lt;/p&gt;

&lt;p&gt;Copy the IDs from the last answer into the &lt;code&gt;profile_id&lt;/code&gt; fields of your &lt;code&gt;profiles.json&lt;/code&gt;. Then sign in by hand: the admin profile gets your staff login, each test profile gets its test shopper. The sessions stay in the profile from then on.&lt;/p&gt;

&lt;p&gt;For separate stores, the same pattern gets shorter: one group per merchant, one profile per store, and the admin and storefront hosts of that store in the same &lt;code&gt;profiles.json&lt;/code&gt; entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit every profile for cookie bleed
&lt;/h2&gt;

&lt;p&gt;Profiles keep sessions apart only as long as people use them correctly. The most common slip is opening a different storefront inside the wrong profile, say checking the US checkout from the German test profile "just for a second". From then on that profile carries US cookies, and the next German test isn't clean.&lt;/p&gt;

&lt;p&gt;This script opens each profile through BitBrowser's Local API, attaches Playwright over CDP, reads every cookie, and flags the ones whose domain belongs to another entry in &lt;code&gt;profiles.json&lt;/code&gt;. A cookie set on a parent domain that covers the profile's own host (like &lt;code&gt;.acme-shop.example&lt;/code&gt;) is not flagged.&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check that each BitBrowser profile only holds cookies for its own stores.

Exit codes: 0 all clean, 1 at least one profile holds another store&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s cookies,
2 no leaks found but some profiles could not be checked.
Run it when nobody is working in the profiles: it opens and closes each one.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;

&lt;span class="n"&gt;API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:54345&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BITBROWSER_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# set it if authentication is on
&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;TOKEN&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;covers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie_domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cookie_domain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;foreign_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;others&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;covers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hosts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;others&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;covers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;domain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&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;profile_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/browser/open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;msg&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_over_cdp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;contexts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/browser/close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;leaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright&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;pw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;entry&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;cookies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="p"&gt;,&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;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profile_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# keep going, report at the end
&lt;/span&gt;                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SKIP  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;foreign_cookies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK    &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; cookies, none from other stores&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;leaks&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;domains&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LEAK  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;label&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: cookies from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;leaks&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skipped&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profiles.json&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;A run against a small &lt;code&gt;profiles.json&lt;/code&gt; prints something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LEAK  acme-de-global: cookies from acme-us-global (us.acme-shop.example)
OK    acme-us-global: 9 cookies, none from other stores
SKIP  client-b-admin: profile not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a profile leaks, the fix is boring and reliable: clear that profile's cookies for the foreign domain, or delete the profile and let the agent recreate it from the same prompt. The exit codes make the script easy to drop into a weekly scheduled job.&lt;/p&gt;

&lt;p&gt;Both scripts were run against local stand-ins for the BigCommerce and BitBrowser APIs (with real Chromium instances holding test cookies), not against a live store, so try them on one profile before pointing them at a client's setup. If you work with attribution across many profiles, there is a longer write-up on &lt;a href="https://identitylayer.hashnode.dev/multi-account-attribution-without-cross-profile-cookie-bleed" rel="noopener noreferrer"&gt;cross-profile cookie bleed in multi-account attribution&lt;/a&gt; that covers the analytics side of the same problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2F372jfecmlgrx12id64cp.png" class="article-body-image-wrapper"&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%2Farticles%2F372jfecmlgrx12id64cp.png" alt="Three BitBrowser profiles tiled side by side, each signed in to a different BigCommerce storefront" width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the customer login rules
&lt;/h2&gt;

&lt;p&gt;With clean, located profiles, the MSF customer rules become a short test matrix.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Customer&lt;/th&gt;
&lt;th&gt;&lt;code&gt;channel_ids&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;US storefront (allows global logins)&lt;/th&gt;
&lt;th&gt;DE storefront (global logins off)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Global test shopper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;null&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;can sign in&lt;/td&gt;
&lt;td&gt;refused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DE-only test shopper&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[3]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;refused&lt;/td&gt;
&lt;td&gt;can sign in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Explicit both&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[1, 3]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;can sign in&lt;/td&gt;
&lt;td&gt;can sign in&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To switch global logins off for one storefront, call the channel settings endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; PUT &lt;span class="s2"&gt;"https://api.bigcommerce.com/stores/&lt;/span&gt;&lt;span class="nv"&gt;$BC_STORE_HASH&lt;/span&gt;&lt;span class="s2"&gt;/v3/customers/settings/channels/3"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Auth-Token: &lt;/span&gt;&lt;span class="nv"&gt;$BC_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"allow_global_logins": false}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let the agent lay out the windows so you can read both results at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open acme-us-global and acme-de-global and place them side by side on my main screen.

Close every open profile tagged bc-test.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sign in with the same test shopper in both windows and compare. Each profile has its own cookies and proxy, so neither login touches the other, and any difference in currency or tax you see comes from the storefront settings and the location, not from a leftover session. Open acme-us-from-ca the same way to check the Canadian dollar default.&lt;/p&gt;

&lt;p&gt;Channel IDs above are examples. Read yours from the starter &lt;code&gt;profiles.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile checks on cloud phones
&lt;/h2&gt;

&lt;p&gt;Desktop profiles cover most storefront testing, but a mobile checkout behaves differently: smaller layouts, the system keyboard over form fields, and payment sheets that only appear on a real phone browser.&lt;/p&gt;

&lt;p&gt;A cloud phone is a remote Android or iOS device you open from your desktop. When BitBrowser is already running your profiles, &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; for Android and &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone-iOS&lt;/a&gt; for iPhone are the natural pick, because the same MCP server that builds your profiles can start and stop the phones too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open the cloud phone named qa-android-de.

Shut down every running cloud phone and tell me which ones were on.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Give each storefront market its own phone, the same way you gave it its own desktop profile, and the German mobile checkout stays separate from the US one. Shutting phones down at the end of a session is worth turning into habit, since a forgotten device keeps running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Storefront or separate stores
&lt;/h2&gt;

&lt;p&gt;If the brands share most of their catalog and one team runs them, MSF saves a lot of duplicated work. One product update reaches every storefront it is assigned to, one app install covers the store (if the app supports MSF), and reporting starts in one place. Your session setup is one admin profile and a set of test profiles.&lt;/p&gt;

&lt;p&gt;Pick separate stores when the walls matter more than the shared work. That usually means different legal entities, different teams that shouldn't see each other's orders, or a contractor who must only touch one brand. Global staff permissions in MSF make that last case impossible inside one store. Your session setup is one profile per store, and there is less to audit.&lt;/p&gt;

&lt;p&gt;A mixed setup is normal too. Plenty of merchants run their main brands in one MSF store and keep a wholesale or outlet operation in a store of its own. In that case, give each store its own BitBrowser group and run the cookie audit across all of them, since leaks between stores are the ones that hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;/p&gt;
  Does BigCommerce Multi-Storefront share customer accounts between storefronts?
  &lt;br&gt;
By default, yes. New customers have &lt;code&gt;channel_ids&lt;/code&gt; set to &lt;code&gt;null&lt;/code&gt;, which lets them sign in to every channel that allows global logins, and the default storefront allows them. Set &lt;code&gt;allow_global_logins&lt;/code&gt; to &lt;code&gt;false&lt;/code&gt; on a channel, or give customers a specific &lt;code&gt;channel_ids&lt;/code&gt; list, to keep accounts apart.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Can I limit a staff user to one storefront in Multi-Storefront?
  &lt;br&gt;
No. User permissions are global across all storefronts in an MSF store. If a person must only see one brand, that brand needs its own store.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Do two storefronts in one browser share a cart?
  &lt;br&gt;
Not when they are on different domains, because the session cookie belongs to each storefront's domain. They still share the browser's IP address, language and any leftover cookies, which is what spoils location and login tests.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  What does BitBrowser MCP actually do here?
  &lt;br&gt;
It lets an AI client create, tag, group, open, close and arrange browser profiles and cloud phones, and set or check their proxies. It doesn't click inside pages or sign in for you.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Do I need a separate proxy for every profile?
  &lt;br&gt;
Only for the profiles where location matters, such as currency and tax tests for a specific country. An admin profile can use your normal connection.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Which MCP clients work with BitBrowser?
  &lt;br&gt;
BitBrowser's guide lists Claude Code, Codex, Cursor, OpenCode and several others, plus a general configuration for any client that supports custom MCP servers.&lt;br&gt;


&lt;p&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I do content work for BitBrowser and BitCloudPhone, and the BitBrowser link in this post is a referral link.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>bigcommerce</category>
      <category>ecommerce</category>
      <category>mcp</category>
      <category>python</category>
    </item>
    <item>
      <title>Building a Proxy Health-Check Dashboard for 1000+ Residential IPs</title>
      <dc:creator>yooexo</dc:creator>
      <pubDate>Wed, 30 Sep 2026 03:24:00 +0000</pubDate>
      <link>https://dev.to/yooexo/building-a-proxy-health-check-dashboard-for-1000-residential-ips-3n93</link>
      <guid>https://dev.to/yooexo/building-a-proxy-health-check-dashboard-for-1000-residential-ips-3n93</guid>
      <description>&lt;p&gt;A rotating residential pool with 1,000 IPs sounds like abundance until you measure it. On any given hour, 4-7% of the pool is broken: unreachable, geo-drifted, ISP mislabeled, or already on someone else's abuse list. Nobody tells you which ones. Your account gets flagged, you check the proxy provider dashboard, and it shows "all systems operational."&lt;/p&gt;

&lt;p&gt;The only fix is your own monitoring stack. This article covers the one that has held up across three production pools: what to check, how often, where to store the data, which Grafana panels actually catch incidents before users do, and how the health signal loops back into the &lt;a href="https://www.bitbrowser.net/" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; profile fleet so bad IPs stop getting reassigned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "proxy health" actually means
&lt;/h2&gt;

&lt;p&gt;Proxy health is not a single metric. It is a rolling composite of five signals, each of which fails independently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connectivity.&lt;/strong&gt; Does the proxy answer a TCP handshake in under 3 seconds?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Egress IP correctness.&lt;/strong&gt; When you route a request through it, does the destination see the IP the provider promised?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Geolocation accuracy.&lt;/strong&gt; Does the IP resolve to the country and region the provider claims?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ISP classification.&lt;/strong&gt; Does the IP register as &lt;code&gt;residential&lt;/code&gt; (or &lt;code&gt;mobile&lt;/code&gt;) rather than &lt;code&gt;hosting&lt;/code&gt; or &lt;code&gt;datacenter&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation status.&lt;/strong&gt; Is the IP on Spamhaus DROP, on Scamalytics' fraud list, or blocked by Cloudflare's threat score?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A proxy can pass 1-3 and fail 4-5, and the failure is invisible until an account signup gets a CAPTCHA loop or a payment gateway declines the transaction. All five need continuous checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The metrics catalog
&lt;/h2&gt;

&lt;p&gt;The dashboard tracks eleven metrics per IP. Nine come from active probes, two from the fleet feedback loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;proxy_up&lt;/code&gt; (0 or 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_latency_ms&lt;/code&gt; (histogram)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_egress_ip_match&lt;/code&gt; (0 or 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_geo_country&lt;/code&gt;, &lt;code&gt;proxy_geo_region&lt;/code&gt; (labels)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_isp_type&lt;/code&gt; (residential, mobile, hosting, unknown)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_reputation_score&lt;/code&gt; (0-100, lower is worse)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_blocklist_hits&lt;/code&gt; (count of lists flagging the IP)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;proxy_last_check_timestamp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_bitbrowser_profile_flags_24h&lt;/code&gt; (from the fleet feedback loop)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_bitbrowser_session_failures_24h&lt;/code&gt; (from the fleet feedback loop)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Eleven metrics, one label set per IP, roughly 11,000 time series for a 1,000-IP pool. Prometheus handles that on a t3.medium without breaking a sweat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Proxy pool (Proxy-Seller, IPRoyal, Bright Data, ...)
                    │
                    ▼
   ┌───────────────────────────────┐
   │      Check worker (Node.js)   │
   │  ─ Round-robin scheduler       │
   │  ─ HTTP probe (via proxy)      │
   │  ─ Geo lookup (ipinfo.io)      │
   │  ─ Reputation lookup           │
   │  ─ Prometheus /metrics         │
   └───────────────┬───────────────┘
                   │  scrape /metrics every 15s
                   ▼
   ┌───────────────────────────────┐
   │        Prometheus              │
   │  ─ 30d retention               │
   │  ─ Alertmanager rules          │
   └───────────────┬───────────────┘
                   │
                   ▼
   ┌───────────────┴───────────────┐
   │            Grafana             │
   │  ─ Pool overview               │
   │  ─ IP drill-down               │
   │  ─ Provider comparison         │
   └────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One check worker per 500 IPs. Two workers cover a 1,000-IP pool with room to spare. Prometheus scrapes both workers on the same interval so metrics stay aligned.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check worker
&lt;/h2&gt;

&lt;p&gt;Node.js with &lt;code&gt;axios&lt;/code&gt;, &lt;code&gt;prom-client&lt;/code&gt;, and a small round-robin queue. The core loop for one proxy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpsProxyAgent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https-proxy-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;prom-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxyUp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gauge&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxy_up&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 if the proxy answered, 0 otherwise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;labelNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxy_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;geo_expected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;proxyLatency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Histogram&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxy_latency_ms&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;help&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Round-trip latency to the probe endpoint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;labelNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;proxy_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;buckets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpsProxyAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;proxy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;geo_expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geo&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://ipinfo.io/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;httpsAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;proxyUp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;proxyLatency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;proxy_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;elapsed&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;proxyUp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;};&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;The probe endpoint is &lt;code&gt;ipinfo.io/json&lt;/code&gt; because it returns IP, country, region, city, and ASN in a single call. Free tier gives 50,000 requests per month, enough for a 1,000-IP pool checked every 5 minutes.&lt;/p&gt;

&lt;p&gt;Reputation lookups are more expensive. Batch them: pull every distinct IP from the pool once per hour, hit IPQualityScore or Scamalytics in bulk mode, cache results for 60 minutes in Redis. This keeps the reputation API bill under $30/month even at 1,000 IPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storage choice
&lt;/h2&gt;

&lt;p&gt;Prometheus over InfluxDB, TimescaleDB, or Victoria Metrics for a pool this size. Reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The metric cardinality (11 metrics × 1,000 IPs = 11k series) sits comfortably in the middle of Prometheus's sweet spot&lt;/li&gt;
&lt;li&gt;Grafana's Prometheus data source is the most polished&lt;/li&gt;
&lt;li&gt;The Prometheus query language handles the "IPs currently dead" and "IPs that failed 3 checks in a row" queries in one line each&lt;/li&gt;
&lt;li&gt;Alertmanager ships with Prometheus and needs no extra binary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retention: 30 days. Anything older than a month is not useful for triage and takes up disk. If you need long-term trends, export daily aggregates to a Postgres table and query that instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Grafana dashboard
&lt;/h2&gt;

&lt;p&gt;Four panels. That is it. Adding more panels reduces incident response speed, not increases it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panel 1: Pool health heatmap.&lt;/strong&gt; X-axis is time (last 6 hours). Y-axis is &lt;code&gt;proxy_id&lt;/code&gt;. Cell color is &lt;code&gt;proxy_up&lt;/code&gt;. A sudden vertical stripe of red means a provider outage. A slowly growing pattern of red dots means IPs are being burned individually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panel 2: Failed-probe rate per provider.&lt;/strong&gt; Line graph, one line per provider (Proxy-Seller, IPRoyal, NetNut, DataImpulse). Y-axis is &lt;code&gt;(1 - avg(proxy_up)) * 100&lt;/code&gt;. Any line crossing 8% is a signal to open a support ticket with that provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panel 3: Geo drift table.&lt;/strong&gt; A table of IPs where &lt;code&gt;proxy_geo_country != geo_expected&lt;/code&gt;. If a US IP starts resolving to Panama, remove it from the pool immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Panel 4: Correlation with BitBrowser flags.&lt;/strong&gt; Scatter plot: X-axis is &lt;code&gt;proxy_reputation_score&lt;/code&gt;, Y-axis is &lt;code&gt;proxy_bitbrowser_profile_flags_24h&lt;/code&gt;. The cluster of high-flag / low-reputation IPs in the bottom-right is your daily kill list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alertmanager rules
&lt;/h2&gt;

&lt;p&gt;Three alerts, no more. Every additional alert dilutes the ones that matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy-health&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyPoolDegraded&lt;/span&gt;
    &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(1 - avg by (provider) (proxy_up)) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;0.08&lt;/span&gt;
    &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;$labels.provider&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;pool&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;8%&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;down&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;10&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;minutes"&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyGeoDrift&lt;/span&gt;
    &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count(proxy_geo_country != on(proxy_id) group_left proxy_geo_expected) &amp;gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;
    &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;warning&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ProxyReputationCollapse&lt;/span&gt;
    &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;avg by (provider) (proxy_reputation_score) &amp;lt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;
    &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;15m&lt;/span&gt;
    &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;critical&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Route them to a dedicated Slack channel that only fires on proxy issues. Muted-by-default. Anyone whose account gets flagged checks that channel first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feeding the signal back into BitBrowser
&lt;/h2&gt;

&lt;p&gt;The dashboard is not the endpoint. It is the input to a decision loop. A small service (also Node.js, also under 300 lines) subscribes to Prometheus queries every 5 minutes and, for any IP flagged as &lt;code&gt;unhealthy&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calls the BitBrowser LocalAPI to find every profile bound to that IP&lt;/li&gt;
&lt;li&gt;Marks each profile as &lt;code&gt;proxy_stale&lt;/code&gt; in a local database&lt;/li&gt;
&lt;li&gt;Prevents the dispatcher from opening those profiles until a new IP is assigned&lt;/li&gt;
&lt;li&gt;Requests a fresh IP from the provider's rotation API&lt;/li&gt;
&lt;li&gt;Updates the profile via &lt;code&gt;/browser/update&lt;/code&gt; with the new proxy config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This closes the loop. A dying IP takes profiles offline in under 5 minutes instead of after a user complaint. Protocol-level nuances (why HTTP vs SOCKS5 vs Shadowsocks matter here, especially for mobile carrier detection) are covered in &lt;a href="https://hackmd.io/@DigitalGrowthPro/proxy-protocol-comparison-http-socks5-shadowsocks" rel="noopener noreferrer"&gt;this proxy protocol comparison&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where cloud phones fit in the same pipeline
&lt;/h2&gt;

&lt;p&gt;Mobile pools have a different failure mode: the phone itself can be fine while the carrier's IP block goes on Snapchat's ban list. Extending the dashboard to cover &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone Android&lt;/a&gt; and &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt; devices means adding two more probe types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App-store reachability check.&lt;/strong&gt; Every 15 minutes, from each cloud phone, hit the App Store or Play Store discovery endpoint. A 403 or 429 means the device's carrier IP is soft-blocked at the app layer, invisible from web-only probes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-app CAPTCHA rate.&lt;/strong&gt; Track how often accounts running on a given device hit CAPTCHAs. Above a threshold, cycle the device.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both probes return the same Prometheus metric names with a &lt;code&gt;platform=ios&lt;/code&gt; or &lt;code&gt;platform=android&lt;/code&gt; label. One dashboard covers three worlds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling notes
&lt;/h2&gt;

&lt;p&gt;The stack above sizes to roughly 5,000 IPs on a single Prometheus instance and a two-worker check fleet. Beyond that:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Bottleneck&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Check worker&lt;/td&gt;
&lt;td&gt;Node event loop under 2,000 concurrent HTTP calls&lt;/td&gt;
&lt;td&gt;Split into worker pool via &lt;code&gt;piscina&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prometheus&lt;/td&gt;
&lt;td&gt;Series cardinality above 100k&lt;/td&gt;
&lt;td&gt;Shard by provider onto separate Prometheus instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reputation API&lt;/td&gt;
&lt;td&gt;Cost per lookup&lt;/td&gt;
&lt;td&gt;Extend cache TTL, batch harder, drop reputation for internal-only pools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Grafana&lt;/td&gt;
&lt;td&gt;Panel query time above 3 seconds&lt;/td&gt;
&lt;td&gt;Add recording rules for expensive expressions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 10,000 IPs the architecture pattern is the same, the numbers on each box change.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which reputation service is worth paying for?&lt;/strong&gt;&lt;br&gt;
IPQualityScore for general fraud scoring, Scamalytics for stricter e-commerce use cases, and MaxMind minFraud when payment risk is the specific concern. All three have similar accuracy; the choice comes down to which one your target platforms use themselves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I skip reputation checks entirely?&lt;/strong&gt;&lt;br&gt;
For internal automation that never touches a payment gateway or an identity-verification vendor, yes. For anything that touches Stripe, Facebook Ads, or account creation on a Tier-1 platform, no. The reputation check catches issues 24-48 hours before the destination platform's own risk engine does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if my proxy provider does not expose sticky sessions?&lt;/strong&gt;&lt;br&gt;
The dashboard still works, but the metric labeling shifts. Instead of &lt;code&gt;proxy_id&lt;/code&gt;, label by &lt;code&gt;session_token&lt;/code&gt; and expect a much higher series churn. Prometheus handles it up to about 20,000 unique tokens per hour.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I probe from the same geo as the proxy claims to be in?&lt;/strong&gt;&lt;br&gt;
For most checks the probe origin does not matter — &lt;code&gt;ipinfo.io&lt;/code&gt; returns the same data whichever direction the query comes from. For app-store and CAPTCHA checks, the probe must originate from the proxy itself; that is what the cloud phone probes cover.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this replace the proxy provider's own dashboard?&lt;/strong&gt;&lt;br&gt;
Yes, and you should stop trusting the provider's dashboard the day you have your own. Provider dashboards report internal health. What matters is what the destination platforms see, and only your own probes measure that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to take it from here
&lt;/h2&gt;

&lt;p&gt;Start with 50 IPs from one provider and the four Grafana panels. Prove the check worker stays under 10% CPU. Prove the alert rules fire on real outages, not on transient blips. Then add the second provider, then the third, then the cloud phone probes.&lt;/p&gt;

&lt;p&gt;Every monitoring stack I have shipped in production started as one worker checking one pool with two panels. The stack that tried to cover everything from day one is still in a Git branch nobody merged.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Affiliate disclosure: This post contains affiliate links. If you sign up for a paid BitBrowser or BitCloudPhone plan through the links above, I may earn a small commission at no additional cost to you. All numbers above come from monitoring stacks I run against my own paid accounts.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>grafana</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Wildberries Multi-Seller: A Complete Guide to the Anti-Detect Browser</title>
      <dc:creator>yooexo</dc:creator>
      <pubDate>Tue, 18 Aug 2026 17:36:26 +0000</pubDate>
      <link>https://dev.to/yooexo/wildberries-multi-seller-a-complete-guide-to-the-anti-detect-browser-23ak</link>
      <guid>https://dev.to/yooexo/wildberries-multi-seller-a-complete-guide-to-the-anti-detect-browser-23ak</guid>
      <description>&lt;p&gt;Wildberries blocked over &lt;strong&gt;14,000 seller accounts&lt;/strong&gt; in Q1 2026 alone, according to their published seller report. Most of those bans weren't for fake products or review fraud. They were triggered by what the platform calls "associated account detection": two or more stores sharing the same device fingerprint, IP range, or browser session data.&lt;/p&gt;

&lt;p&gt;If you run more than one Wildberries store, or you're planning to, this guide covers the exact tools and setup that keep accounts isolated. You need an antidetect browser for desktop management, a cloud phone for mobile app access, and a dedicated Russian residential proxy per account. Read on for the specifics.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is an antidetect browser, and why do Wildberries sellers need one?
&lt;/h2&gt;

&lt;p&gt;A regular browser (Chrome, Firefox, Safari) sends a fingerprint to every website you visit. That fingerprint includes your screen resolution, installed fonts, canvas rendering hash, WebGL vendor string, time zone, language settings, and about 40 other parameters. Wildberries logs this fingerprint when you log into the seller portal.&lt;/p&gt;

&lt;p&gt;Open a second Wildberries seller account on the same device and the same browser, the fingerprint matches. Their system flags both accounts as related and suspends them simultaneously.&lt;/p&gt;

&lt;p&gt;An antidetect browser solves this by creating &lt;strong&gt;isolated browser profiles&lt;/strong&gt;, each with its own fabricated fingerprint. Account A runs in Profile 1, which shows a Windows 10 machine with a 1920×1080 screen, Calibri font, and a specific canvas hash. Account B runs in Profile 2: different OS, different screen resolution, different canvas signature. Each profile stores cookies and local storage separately, so there's no bleed between sessions.&lt;/p&gt;

&lt;p&gt;The antidetect browser doesn't just change the user-agent string. That's what incognito mode does, and it doesn't work. It replaces the entire device identity at the API level.&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2Fus77hzro4z3tiy8f4ske.png" class="article-body-image-wrapper"&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%2Farticles%2Fus77hzro4z3tiy8f4ske.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How Wildberries actually detects related seller accounts
&lt;/h2&gt;

&lt;p&gt;Before setting up anything, it helps to know what you're defending against. Wildberries' association detection looks at several layers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser fingerprint matching.&lt;/strong&gt; Canvas hash, WebGL renderer, AudioContext fingerprint, font enumeration, and screen parameters. Two accounts with identical values on three or more of these get flagged automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP address correlation.&lt;/strong&gt; Two accounts logging in from the same IP address on the same day is a hard trigger. Even if the fingerprints differ, shared IPs create an "association chain" in their system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cookie and localStorage sharing.&lt;/strong&gt; If your browsers share session cookies (common when running multiple tabs), account A's session data can leak into account B's requests. Regular Chrome profiles don't isolate these.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavioral timing patterns.&lt;/strong&gt; Wildberries tracks login times. If Account A and Account B both log in every morning between 9:00 and 9:05 Moscow time from the same approximate IP range, their system marks that as suspicious even without a direct fingerprint match.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Payment method overlap.&lt;/strong&gt; Accounts that list the same bank card, phone number, or OGRN get linked in their seller database regardless of technical isolation. This one has nothing to do with browsers. It's a business data problem.&lt;/p&gt;

&lt;p&gt;A proper antidetect browser handles the first three. A proxy handles the fourth (partially). Business data separation handles the fifth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Which antidetect browser works best for Wildberries in 2026?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.bitbrowser.net" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; is the strongest option for Wildberries multi-seller setups right now, and it holds that position for three specific reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, it supports Russian-locale fingerprints natively.&lt;/strong&gt; You can set the browser language to &lt;code&gt;ru-RU&lt;/code&gt;, the time zone to &lt;code&gt;Europe/Moscow&lt;/code&gt;, and the keyboard layout to Russian inside each profile. When Wildberries reads your browser's &lt;code&gt;navigator.language&lt;/code&gt; property, it gets &lt;code&gt;ru-RU&lt;/code&gt; instead of something that contradicts your Russian IP. Mismatched locale and IP is one of the easier ways to get flagged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Second, BitBrowser has a free tier with up to 10 profiles.&lt;/strong&gt; That covers most Wildberries multi-sellers who run 3 to 8 stores. You don't need to pay to test the setup or run a small operation. Paid plans start at around $10 per month for more profiles and team access features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third, it passes standard fingerprint audit tools.&lt;/strong&gt; Running a fresh BitBrowser profile through BrowserLeaks, CreepJS, and Pixelscan should show no automation flags, no WebDriver property exposed, and no canvas or WebGL anomalies. Test this before you log into any real account.&lt;br&gt;
&lt;a href="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%2Farticles%2F15ek4f7hgao8zsdefxcs.webp" class="article-body-image-wrapper"&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%2Farticles%2F15ek4f7hgao8zsdefxcs.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup process for a Wildberries profile in BitBrowser takes about 4 minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new profile. Name it after the store (e.g., "WB Store 2: Electronics").&lt;/li&gt;
&lt;li&gt;Set OS to Windows 10 or Windows 11. Russian sellers overwhelmingly use Windows, so matching that reduces fingerprint suspicion.&lt;/li&gt;
&lt;li&gt;Set language to &lt;code&gt;ru-RU&lt;/code&gt;, time zone to &lt;code&gt;Europe/Moscow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Assign your proxy (covered in the next section).&lt;/li&gt;
&lt;li&gt;Open the profile, go to &lt;code&gt;browserleaks.com&lt;/code&gt;, and confirm the IP, locale, and time zone all match before logging in.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One thing to do before running the profile: open &lt;code&gt;chrome://settings/&lt;/code&gt; inside the profile and disable any auto-fill or sync features. BitBrowser profiles are isolated from Chrome sync, but disabling it explicitly prevents unexpected data requests.&lt;/p&gt;


&lt;h2&gt;
  
  
  What proxy setup do you need for Wildberries accounts?
&lt;/h2&gt;

&lt;p&gt;The proxy choice matters as much as the browser. A good antidetect browser with a bad proxy still gets flagged. For Wildberries, the rules are clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use Russian residential proxies.&lt;/strong&gt; Wildberries is headquartered in Moscow and most of its seller base operates from Russian ISPs. A login from a US datacenter IP triggers immediate scrutiny. A residential IP from Rostelecom or MTS looks like a normal Russian internet connection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One dedicated IP per account.&lt;/strong&gt; Shared proxy pools rotate IPs between users, which means Account A might occasionally get the same exit IP as another seller's Account B. Dedicated IPs cost a bit more but the isolation is worth it for accounts generating real revenue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For mobile app access, use Russian mobile proxies.&lt;/strong&gt; 4G proxies from Russian mobile carriers (MTS, Megafon, Beeline) give you a mobile ASN, which matches the expected IP type when logging in through a phone or cloud phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This dev.to article covers how to architect proxy rotation if you're managing a larger number of accounts programmatically:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/digitalgrowthpro/building-a-proxy-rotation-system-for-web-automation-architecture-patterns-and-implementation-1l8m" class="crayons-story__hidden-navigation-link"&gt;Building a Proxy Rotation System for Web Automation: Architecture Patterns and Implementation&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 href="/digitalgrowthpro" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3576363%2F33253307-d026-4f2c-a9a6-a0f3fd7bdefa.jpg" alt="digitalgrowthpro profile" class="crayons-avatar__image" width="800" height="770"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/digitalgrowthpro" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Digital Growth Pro
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Digital Growth Pro
                
                
              
              &lt;div id="story-author-preview-content-3170008" 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="/digitalgrowthpro" 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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3576363%2F33253307-d026-4f2c-a9a6-a0f3fd7bdefa.jpg" class="crayons-avatar__image" alt="" width="800" height="770"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Digital Growth Pro&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;/div&gt;
          &lt;a href="https://dev.to/digitalgrowthpro/building-a-proxy-rotation-system-for-web-automation-architecture-patterns-and-implementation-1l8m" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 13&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/digitalgrowthpro/building-a-proxy-rotation-system-for-web-automation-architecture-patterns-and-implementation-1l8m" id="article-link-3170008"&gt;
          Building a Proxy Rotation System for Web Automation: Architecture Patterns and Implementation
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/proxy"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;proxy&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&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/digitalgrowthpro/building-a-proxy-rotation-system-for-web-automation-architecture-patterns-and-implementation-1l8m#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;
            8 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;For manual multi-seller setups, you just need static residential IPs, one per account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Assigning the proxy in BitBrowser:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the profile settings, click the &lt;strong&gt;Proxy&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;SOCKS5&lt;/strong&gt; (preferred) or HTTP.&lt;/li&gt;
&lt;li&gt;Enter the IP, port, username, and password from your proxy provider.&lt;/li&gt;
&lt;li&gt;Save, then click &lt;strong&gt;Check Proxy&lt;/strong&gt; inside BitBrowser. It shows you the resolved IP and country. Confirm it shows &lt;code&gt;RU&lt;/code&gt; before using the profile.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Do you need a cloud phone for Wildberries?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="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%2Farticles%2F0enpfz3zxwtwxcumitbw.webp" class="article-body-image-wrapper"&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%2Farticles%2F0enpfz3zxwtwxcumitbw.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Desktop browser management covers the Wildberries Partner Portal (&lt;code&gt;partners.wildberries.ru&lt;/code&gt;) but the mobile app is a separate channel. Many sellers use the Wildberries Seller app on iOS or Android to handle order fulfillment, buyer messages, and listing updates. The app fingerprints the device separately from the browser: UDID, device model, OS version, and screen resolution.&lt;/p&gt;

&lt;p&gt;Log into two different Wildberries seller accounts from the same physical iPhone and those accounts get linked at the device level. The fix is a cloud phone: a virtual mobile device hosted remotely, with its own isolated device identity and a separate IP assignment.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; (Android) and &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt; handle this for Wildberries sellers. Each cloud phone instance has its own device fingerprint, its own app environment, and accepts its own proxy assignment. You run it from your regular browser and it presents as a completely separate physical device to Wildberries.&lt;/p&gt;

&lt;p&gt;The iOS version specifically matters for the Russian market because iOS penetration among Russian e-commerce buyers and sellers is high. Wildberries iOS app updates ship regularly and sometimes add new device checks. BitCloudPhone iOS keeps pace with those updates. You're not jailbreaking a phone or running an emulator that an app can detect as virtual. The cloud phone environment is designed to look like real device hardware.&lt;/p&gt;

&lt;p&gt;For a Wildberries multi-seller operation running 5 stores, the typical setup is:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Per account&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Partner Portal (desktop)&lt;/td&gt;
&lt;td&gt;BitBrowser profile&lt;/td&gt;
&lt;td&gt;1 Russian residential IP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile seller app&lt;/td&gt;
&lt;td&gt;BitCloudPhone iOS&lt;/td&gt;
&lt;td&gt;1 Russian 4G mobile IP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You access both from the same laptop. The cloud phones are remote, so there's no local hardware fingerprint from your actual machine.&lt;/p&gt;




&lt;h2&gt;
  
  
  What account-level separation does Wildberries require?
&lt;/h2&gt;

&lt;p&gt;Technical isolation through an antidetect browser gets you past fingerprint detection. Business data separation keeps you clean at the account-record level. These are different problems.&lt;/p&gt;

&lt;p&gt;For each Wildberries seller account, Wildberries stores: the seller's legal entity (IP or OOO), the OGRN/OGRNIP number, the bank account tied to payouts, the phone number for verification, and the email address. Accounts sharing any of these get linked in Wildberries' seller database regardless of how well your browser profiles are isolated.&lt;/p&gt;

&lt;p&gt;Each store needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Its own phone number for SMS verification&lt;/li&gt;
&lt;li&gt;Its own email address (different domain if possible; similar patterns like &lt;code&gt;store1@gmail.com&lt;/code&gt; and &lt;code&gt;store2@gmail.com&lt;/code&gt; draw less scrutiny than exact matches, but separate domains are cleaner)&lt;/li&gt;
&lt;li&gt;Its own payout bank card or account&lt;/li&gt;
&lt;li&gt;Its own legal entity (IP or OOO registration with a unique OGRN)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most Russian multi-sellers operating legally use separate sole proprietor (IP) registrations or different OOO entities. The point is that the paper trail for each account has to be genuinely distinct.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to warm up a new Wildberries seller account
&lt;/h2&gt;

&lt;p&gt;Wildberries treats brand-new seller accounts with a lower trust score. Account-association detection is more sensitive during the first 30 days. Three things help here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log in gradually at first.&lt;/strong&gt; Don't open all 5 accounts on day one. Start with one, let it run for a week with normal activity, then add the second account, and so on. Accounts that start simultaneously from different profiles on the same day raise a pattern flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match your activity hours to the account's supposed location.&lt;/strong&gt; If the account's store is listed as being based in Novosibirsk (UTC+7), seller login activity should happen during Novosibirsk business hours, not 2:00 AM Moscow time. BitBrowser lets you set the time zone per profile. Use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't copy listings between accounts during the warm-up period.&lt;/strong&gt; Two accounts listing the exact same product with the exact same title, photos, and description on the same day. Wildberries' content matching system flags them as related. Differentiate the listings even for the same product: different main image, different title structure, different keyword emphasis.&lt;/p&gt;




&lt;h2&gt;
  
  
  Browser automation for Wildberries: is it worth it?
&lt;/h2&gt;

&lt;p&gt;Some sellers automate Wildberries seller tasks: bulk listing uploads, price updates, stock adjustments. BitBrowser exposes a local REST API that automation tools can call to create profiles, assign proxies, and open sessions. You can build scripts using Selenium, Playwright, or Puppeteer that connect to BitBrowser's existing browser instances rather than launching a detectable headless browser.&lt;/p&gt;

&lt;p&gt;The advantage of going through BitBrowser's API rather than directly launching Playwright is that the browser instance already has a full human fingerprint. A standard &lt;code&gt;playwright.chromium.launch(headless=True)&lt;/code&gt; exposes the &lt;code&gt;navigator.webdriver&lt;/code&gt; flag that Wildberries can read. A session launched through BitBrowser's API inherits the profile's fingerprint and doesn't set that flag.&lt;/p&gt;

&lt;p&gt;This dev.to article walks through the technical connection pattern for setting up automated sessions with real fingerprints:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i" class="crayons-story__hidden-navigation-link"&gt;BitBrowser + Selenium: Setting Up Automated Browser Sessions with Real Fingerprints&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 href="/digitalgrowthpro" class="crayons-avatar  crayons-avatar--l  "&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%2F3576363%2F33253307-d026-4f2c-a9a6-a0f3fd7bdefa.jpg" alt="digitalgrowthpro profile" class="crayons-avatar__image" width="800" height="770"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/digitalgrowthpro" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Digital Growth Pro
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Digital Growth Pro
                
                
              
              &lt;div id="story-author-preview-content-3512272" 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="/digitalgrowthpro" 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%2F3576363%2F33253307-d026-4f2c-a9a6-a0f3fd7bdefa.jpg" class="crayons-avatar__image" alt="" width="800" height="770"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Digital Growth Pro&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;/div&gt;
          &lt;a href="https://dev.to/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 16&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/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i" id="article-link-3512272"&gt;
          BitBrowser + Selenium: Setting Up Automated Browser Sessions with Real Fingerprints
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/selenium"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;selenium&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/automation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;automation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/security"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;security&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/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i#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;
            6 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;For most Wildberries multi-sellers, full automation isn't necessary. Manual management of 5 to 8 accounts through BitBrowser profiles takes maybe 90 minutes per day across all stores. Automation becomes worthwhile at 15+ accounts or when you need bulk price updates across hundreds of SKUs.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happens if one account gets flagged?
&lt;/h2&gt;

&lt;p&gt;Set up proper isolation and one account still gets a warning or temporary suspension, the other accounts shouldn't be dragged down with it. That's the entire point of isolation. With a proper antidetect browser setup and separate proxies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The flagged account's cookie data doesn't exist in other profiles&lt;/li&gt;
&lt;li&gt;Each account's IP is dedicated, so it can't be traced to other accounts' IPs&lt;/li&gt;
&lt;li&gt;Each profile fingerprint is unique, so Wildberries can't find matches to other stores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The investigation stays contained. You lose one account, not all five.&lt;/p&gt;

&lt;p&gt;Without isolation, running everything on Chrome with a VPN toggle means a single suspension investigation can unravel every related account, because Wildberries can trace the shared fingerprint and IP history through their logs. That's how sellers lose entire portfolios at once.&lt;/p&gt;




&lt;h2&gt;
  
  
  Russian marketplace multi-account management: same rules apply
&lt;/h2&gt;

&lt;p&gt;Everything covered here transfers to Ozon, Avito, and Yandex Market. All three use fingerprinting and IP correlation to detect related accounts. That antidetect browser + Russian residential proxy + separate business data stack works across all of them.&lt;/p&gt;

&lt;p&gt;Wildberries differs from those platforms in one specific way: the mobile app requirement. More Wildberries sellers manage day-to-day operations through the app than through the Partner Portal web interface. That's why the cloud phone component matters more for Wildberries than it does for Ozon, which has a more complete web-based seller interface.&lt;/p&gt;

&lt;p&gt;For sellers managing accounts on multiple Russian marketplaces simultaneously, one practical approach is to extend the naming convention: "WB Store 1," "Ozon Store 1," "Avito Account 1": each gets its own BitBrowser profile and its own proxy, clearly labeled so there's no confusion about which account is active in which profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  What does a full Wildberries multi-seller setup cost?
&lt;/h2&gt;

&lt;p&gt;Here's a rough monthly cost breakdown for running 5 stores:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Monthly cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BitBrowser paid plan (10+ profiles)&lt;/td&gt;
&lt;td&gt;~$10–$20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 dedicated Russian residential proxies&lt;/td&gt;
&lt;td&gt;~$30–$60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BitCloudPhone iOS (5 instances)&lt;/td&gt;
&lt;td&gt;varies by plan, &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;check here&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 virtual phone numbers for verification&lt;/td&gt;
&lt;td&gt;~$5–$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$50–$120&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Against the revenue that 5 active Wildberries stores generate, that's a small operational cost. Losing all accounts to a single suspension wave costs significantly more.&lt;/p&gt;




&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is an antidetect browser and why do Wildberries sellers need one?
&lt;/h3&gt;

&lt;p&gt;An antidetect browser replaces your real device fingerprint with a separate, isolated profile for each session. Wildberries uses fingerprinting to detect related accounts sharing the same device. Running two stores from one regular browser gets both flagged. BitBrowser assigns each store its own unique fingerprint, so the platform sees two completely different devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  How many Wildberries accounts can I safely run with BitBrowser?
&lt;/h3&gt;

&lt;p&gt;BitBrowser's free tier supports up to 10 isolated profiles. Paid plans start around $10/month and scale to hundreds of profiles. Most Wildberries multi-sellers run 3 to 8 accounts per operator without issues, pairing each profile with a dedicated Russian residential IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a cloud phone to manage Wildberries accounts?
&lt;/h3&gt;

&lt;p&gt;Not for the Partner Portal, but yes for the mobile seller app. The iOS and Android apps use device fingerprinting that a desktop browser can't replicate. &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; and &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt; give each account a completely separate device identity, without needing multiple physical phones.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which proxy type works best for Wildberries multi-seller accounts?
&lt;/h3&gt;

&lt;p&gt;Russian residential proxies, one dedicated IP per account. For mobile app access, Russian 4G mobile proxies match the expected mobile ASN. Shared proxies and datacenter IPs both get flagged faster for Wildberries seller logins.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Wildberries detect BitBrowser?
&lt;/h3&gt;

&lt;p&gt;BitBrowser is designed to pass standard fingerprint tests including BrowserLeaks, CreepJS, and Pixelscan. The key is pairing each browser profile with a matching residential proxy and not reusing fingerprint parameters across accounts. Test every new profile before using it on a real account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is using multiple Wildberries accounts against the platform's rules?
&lt;/h3&gt;

&lt;p&gt;Wildberries prohibits one legal entity from operating multiple accounts to exploit promotional systems. Separate legal entities operating separate stores is common practice. The requirement is true isolation: different IPs, different devices or browser profiles, different payment methods, and no shared registration data.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is BitCloudPhone iOS and how does it help Wildberries sellers?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt; is a cloud-hosted iPhone environment. Each instance has its own iOS device fingerprint, UDID, and IP. For sellers managing orders through the iOS Wildberries app, it removes the need for multiple physical phones. Assign a Russian mobile IP and it presents as a real Russian iPhone to Wildberries.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I set up a Russian residential proxy in BitBrowser?
&lt;/h3&gt;

&lt;p&gt;Open your BitBrowser profile settings, go to the Proxy tab, select SOCKS5, paste in your &lt;code&gt;IP:port:user:pass&lt;/code&gt;, and set the location tag to &lt;code&gt;RU&lt;/code&gt;. Save and run a leak test at &lt;code&gt;browserleaks.com&lt;/code&gt; before logging into any Wildberries seller account. The IP shown should be a Russian residential address, not a datacenter range.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Wildberries' detection systems have gotten more precise over the past 18 months. Sellers getting caught aren't running 10 accounts behind a VPN (Wildberries handled that threat years ago). Sophisticated operators are losing accounts now because they missed one layer of isolation: a shared mobile device, a proxy pool that occasionally assigns duplicate IPs, or business registration data that creates a traceable link between otherwise technically separated accounts.&lt;/p&gt;

&lt;p&gt;BitBrowser profiles with Russian residential IPs for the Partner Portal, BitCloudPhone iOS instances with Russian 4G IPs for the mobile app, and genuinely separate business registration data per account. This closes those gaps. Start with the free BitBrowser tier to validate the fingerprint isolation before investing in cloud phones, then scale once you've confirmed the setup passes fingerprint tests.&lt;/p&gt;

&lt;p&gt;For a wider comparison of antidetect browsers beyond Wildberries use cases:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://gotoproxy.com/2026/08/16/best-anti-detect-browsers-in-2026/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgotoproxy.com%2Fwp-content%2Fuploads%2F2026%2F08%2FBest-Anti-Detect-Browsers-in-2026.webp" height="450" class="m-0" width="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://gotoproxy.com/2026/08/16/best-anti-detect-browsers-in-2026/" rel="noopener noreferrer" class="c-link"&gt;
            Best Anti-Detect Browsers in 2026: Why BitBrowser Took the Top Spot This Year - GoToProxy - Expert Proxy Service Reviews &amp;amp; Privacy Tools
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Best Anti-Detect Browsers in 2026: Why BitBrowser Took the Top Spot This Year A hands-on ranking of the 5 anti-detect browsers we tested across 26,400 profiles,
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgotoproxy.com%2Fwp-content%2Fuploads%2F2025%2F05%2Fcropped-gotoproxy.com-favicon-32x32.png" width="32" height="32"&gt;
          gotoproxy.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>tutorial</category>
      <category>russian</category>
    </item>
  </channel>
</rss>
