<?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: Digital Growth Pro</title>
    <description>The latest articles on DEV Community by Digital Growth Pro (@digitalgrowthpro).</description>
    <link>https://dev.to/digitalgrowthpro</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%2F3576363%2F33253307-d026-4f2c-a9a6-a0f3fd7bdefa.jpg</url>
      <title>DEV Community: Digital Growth Pro</title>
      <link>https://dev.to/digitalgrowthpro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/digitalgrowthpro"/>
    <language>en</language>
    <item>
      <title>How to Create and Manage Multiple Bluesky Accounts from a Single Machine</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:46:30 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/how-to-create-and-manage-multiple-bluesky-accounts-from-a-single-machine-51cp</link>
      <guid>https://dev.to/digitalgrowthpro/how-to-create-and-manage-multiple-bluesky-accounts-from-a-single-machine-51cp</guid>
      <description>&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%2Faiqmtp5zqnwek308koin.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%2Faiqmtp5zqnwek308koin.png" alt=" " width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
Bluesky crossed 33 million users in mid-2026, and for creators, agencies, and moderators that number matters. Any social platform of that size eventually attracts people who need to run more than one identity — a personal handle, a brand handle, a niche account for a client. Doing that from one laptop without accounts getting linked takes more than opening a private window.&lt;/p&gt;

&lt;p&gt;This guide walks through the technical setup for running multiple Bluesky accounts safely from a single machine. What Bluesky actually looks at when it flags multi-account activity. Which tools handle isolation properly. And how to pick between an antidetect browser, an Android cloud phone, and an iOS cloud phone depending on how you plan to use the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Bluesky Multi-Account Isn't Like Twitter
&lt;/h2&gt;

&lt;p&gt;Bluesky runs on the AT Protocol, an open federation model where identities live in Personal Data Servers (PDS). That architecture changes what "detecting a second account" means. On centralized platforms, one company owns every signal: your IP, your cookies, your device ID, your login patterns. On Bluesky, the main app.bsky.social host still sees plenty, but the federation piece adds a wrinkle: your handle, DID, and PDS are portable across servers.&lt;/p&gt;

&lt;p&gt;Three practical things follow. First, Bluesky can suspend at the app level even if your DID is portable elsewhere. Second, the moderation team uses IP-level and device-level heuristics like any other platform. Third, because the client is open source and the API is public, Bluesky has less need to hide detection logic behind opaque terms of service. That's helpful when you want to stay compliant while running multiple accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bluesky Actually Sees
&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%2Fdvku8m6dchs7j4q4hzjv.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%2Fdvku8m6dchs7j4q4hzjv.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you open bsky.app in a normal browser, the client sends a predictable set of signals to the server:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP address and geolocation&lt;/li&gt;
&lt;li&gt;User-agent, screen size, timezone, language&lt;/li&gt;
&lt;li&gt;Canvas, WebGL, and audio fingerprints&lt;/li&gt;
&lt;li&gt;Login cookie and session token&lt;/li&gt;
&lt;li&gt;Behavioral patterns: posting rhythm, follow velocity, engagement times&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switch to a second account by logging out and back in on the same browser, and every one of those signals except the cookie stays identical. Two accounts with matching device fingerprints and matching IPs, sharing the same behavioral pattern. Bluesky doesn't need clever machine learning to link them.&lt;/p&gt;

&lt;p&gt;For a longer walkthrough of how platforms build these device graphs, my earlier post on &lt;a href="https://dev.to/digitalgrowthpro/understanding-browser-automation-detection-a-technical-deep-dive-for-developers-l4a"&gt;browser automation detection for developers&lt;/a&gt; covers the actual signals at the API level.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Isolation Problem
&lt;/h2&gt;

&lt;p&gt;To run multiple Bluesky accounts safely from one machine, each account needs its own full identity envelope:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A dedicated IP, ideally a residential proxy, sticky per profile&lt;/li&gt;
&lt;li&gt;A distinct browser fingerprint covering canvas, WebGL, audio, fonts, and screen resolution&lt;/li&gt;
&lt;li&gt;Isolated cookies and local storage per account&lt;/li&gt;
&lt;li&gt;A posting schedule that doesn't mirror the other accounts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Incognito windows and separate Chrome profiles fail two of those. Same fingerprint, same IP. Even a VPN doesn't fix the fingerprint side. What actually works is an antidetect browser that gives every profile its own isolated environment. The same principle applies to e-commerce, dating apps, and ad accounts. I wrote about the pattern for stores in &lt;a href="https://dev.to/digitalgrowthpro/how-to-manage-multiple-e-commerce-stores-without-account-association-in-2025-4gnj"&gt;How to Manage Multiple E-commerce Stores Without Account Association in 2025&lt;/a&gt;, and the mechanics carry over to Bluesky almost line for line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Bluesky With an Antidetect Browser
&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%2Ftxvn1obw1awrsci11e54.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%2Ftxvn1obw1awrsci11e54.png" alt=" " width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For running Bluesky through the web client, an antidetect browser is the right tool for the job. &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; creates isolated Chromium-based profiles, each with its own fingerprint, cookie jar, and proxy assignment. Setup for Bluesky looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new profile in BitBrowser for each Bluesky account.&lt;/li&gt;
&lt;li&gt;Assign a unique residential proxy per profile. City-level matching to where the account "lives" helps for accounts targeting a specific region.&lt;/li&gt;
&lt;li&gt;Set a distinct fingerprint per profile. BitBrowser generates one automatically, but you can adjust user-agent, resolution, timezone, and language to match the proxy geography.&lt;/li&gt;
&lt;li&gt;Open bsky.app inside the profile and log in. Save the session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the core setup. If you're managing more than five or ten accounts, batching profiles and using the API to spin them up in bulk saves hours. Proxy binding done wrong is one of the most common failure points. My earlier piece on &lt;a href="https://dev.to/digitalgrowthpro/building-a-proxy-rotation-system-for-web-automation-architecture-patterns-and-implementation-1l8m"&gt;Building a Proxy Rotation System for Web Automation&lt;/a&gt; covers the architecture patterns worth stealing for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  When You Need a Cloud Phone Instead
&lt;/h2&gt;

&lt;p&gt;Bluesky's mobile app has grown fast. For anything that mimics organic usage (replying in threads, engaging with posts on the go, joining custom feeds from mobile), the mobile client is where real activity happens. Running the mobile app on your own phone caps you at one account per install. Bluesky supports multi-account switching on mobile natively, but every session shares the same device fingerprint, GPS, and carrier data.&lt;/p&gt;

&lt;p&gt;That's where cloud phones come in. A cloud phone is a full mobile OS running on remote hardware, accessible from your desktop. Each cloud phone has its own IMEI, GPS, carrier ID, sensor data, and IP. Two cloud phones running the same app look like two different devices to the platform because they are.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone-Android&lt;/a&gt; covers most Bluesky use cases. It runs real Android devices in the cloud with proxy support per device, so each cloud phone gets its own residential or mobile IP. Setup: create a device, bind a proxy, install the Bluesky app from the built-in marketplace, log in. Repeat per account. From one desktop you can manage dozens of Bluesky accounts, each on a separate Android device with a separate identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The iOS Angle Nobody Talks About
&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%2Fha873j2444qwf6rgxjdh.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%2Fha873j2444qwf6rgxjdh.png" alt=" " width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's where things get interesting for creators targeting US and Western European audiences: a large share of Bluesky's power users are on iOS. iOS accounts, especially ones that need to interact with Apple-specific features or match the demographic of a specific target audience, benefit from actually running on iOS hardware.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone-iOS&lt;/a&gt; launched in July 2026 and runs on real iPhone hardware (iPhone SE2/SE3 clusters) with the native iOS system. Each cloud iPhone has its own dedicated chip, real device identifiers, and full sensor support. From the perspective of Bluesky's mobile client, each cloud phone is a physically distinct iPhone. Proxy assignment is one-click Socks5, and you can operate hundreds of iOS cloud phones from a single computer.&lt;/p&gt;

&lt;p&gt;At the moment, BitCloudPhone-iOS is the only widely available cloud iPhone service on the market. That matters if your account matrix needs iOS-specific behavior signals: the App Store install pattern, iOS-only push notification quirks, the specific way iOS handles background app refresh. Cloud Android services can't replicate any of that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Setup Recommendations
&lt;/h2&gt;

&lt;p&gt;Which tool you actually need depends on your workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Desktop web usage, 5 to 50 accounts → an antidetect browser like BitBrowser with residential proxies. Cheapest per account.&lt;/li&gt;
&lt;li&gt;Mobile organic engagement, mixed audience → an Android cloud phone. Good balance of cost and realism.&lt;/li&gt;
&lt;li&gt;iOS-only demographics, high-value accounts → an iOS cloud phone. More expensive per device but the only tool that gives real iOS fingerprints.&lt;/li&gt;
&lt;li&gt;Hybrid setup → antidetect browser for posting and scheduling, cloud phones for engagement. This is how most agencies actually operate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Proxy Choice Matters More Than People Think
&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%2F61xvytzxanv5rvskdh3n.jpg" 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%2F61xvytzxanv5rvskdh3n.jpg" alt=" " width="720" height="401"&gt;&lt;/a&gt;&lt;br&gt;
Whichever tool you pick, proxy quality is what makes or breaks the setup. Data center proxies get flagged fast on any social platform. For Bluesky specifically, residential proxies with sticky sessions (same IP for 10 or more minutes) are the safe bet. Mobile proxies (real 4G or 5G IPs) work best for cloud phones because they match what a real mobile user's connection looks like at the network layer.&lt;/p&gt;

&lt;p&gt;Never rotate the proxy on an existing profile. Each Bluesky account should be married to one IP for the life of the account. Rotating IPs mid-session is one of the fastest ways to trigger a device-mismatch flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behavior Is the Other Half
&lt;/h2&gt;

&lt;p&gt;Technical isolation only gets you halfway there. Bluesky's moderation team also looks at behavioral patterns: posting velocity, follow bursts, engagement timing. Twenty accounts that all follow the same 100 users in the same order over 24 hours will get flagged regardless of how clean the fingerprints are. Stagger the activity. Vary the posting times. Let accounts develop naturally over weeks, not days.&lt;/p&gt;

&lt;p&gt;Multi-account management on Bluesky is easier right now than on legacy platforms because the moderation stack is younger and less aggressive. But that also means the rules will tighten as the platform grows past 50 million users. Building a clean setup now with per-profile isolation, real device fingerprints, and human-paced behavior is what keeps your accounts alive through the crackdowns that always come.&lt;/p&gt;

</description>
      <category>bluesky</category>
      <category>socialmedia</category>
      <category>privacy</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Managing Multiple Threads Accounts Across Isolated Browser Profiles</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Wed, 12 Aug 2026 15:27:47 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/managing-multiple-threads-accounts-across-isolated-browser-profiles-1p9h</link>
      <guid>https://dev.to/digitalgrowthpro/managing-multiple-threads-accounts-across-isolated-browser-profiles-1p9h</guid>
      <description>&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%2F8t1q3x2fs2l899xdyc9f.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%2F8t1q3x2fs2l899xdyc9f.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
Threads passed 400 million monthly active users in Q1 2026, and Meta now applies the same identity graph it uses on Instagram to link Threads profiles that share device signals. If you manage three Threads accounts from the same laptop and home Wi-Fi, Meta already treats them as one operator, even before any of them post.&lt;/p&gt;

&lt;p&gt;That is the practical problem this guide solves. Whether you run a social media agency, a Web3 community, or a portfolio of niche accounts, the setup below shows how to keep each Threads account on a separate device identity, a separate IP, and a separate cookie jar, using tools built for the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Threads Links Accounts So Aggressively
&lt;/h2&gt;

&lt;p&gt;Threads inherits the Meta detection stack. That stack looks at roughly four layers of signal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Account graph inheritance from Instagram.&lt;/strong&gt; Every Threads account is tied to an Instagram login. Instagram already ships its own device ID (&lt;code&gt;x-mid&lt;/code&gt;), and Threads reads it on first launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser fingerprint.&lt;/strong&gt; Canvas, WebGL, audio context, installed fonts, screen resolution, timezone, and language headers get hashed into a device signature that survives cookie clearing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network ASN and IP reputation.&lt;/strong&gt; Residential IPs from the same /24 block get grouped. Datacenter IPs get flagged on sight for consumer social apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral pattern.&lt;/strong&gt; Post timing, scroll velocity, tap coordinates on mobile, and the interval between actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any two accounts that match on 2 of these 4 layers get soft-linked. Match on 3, and Meta files them as the same operator, which is when checkpoints, phone verification loops, and shadow limits begin.&lt;/p&gt;

&lt;p&gt;If you want a deeper look at how the underlying detection actually works, the &lt;a href="https://dev.to/digitalgrowthpro/how-websites-know-youre-using-multiple-accounts-even-with-a-proxy-5cc9"&gt;signal chain that platforms use to link accounts even behind a proxy&lt;/a&gt; covers the fingerprint layer in more depth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Isolated Profiles" Really Means
&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%2Fw7ru7lpxn2apq6iebbyr.jpg" 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%2Fw7ru7lpxn2apq6iebbyr.jpg" alt=" " width="799" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A tab in incognito mode is not an isolated profile. Neither is a separate Chrome user account. Both share the same underlying browser build, the same GPU driver, and the same operating system font list, which produces the same canvas hash.&lt;/p&gt;

&lt;p&gt;An isolated profile needs, at minimum:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A distinct fingerprint per profile (canvas, WebGL, audio, fonts, timezone, language, screen)&lt;/li&gt;
&lt;li&gt;A dedicated proxy per profile, bound at the profile level, not the tab level&lt;/li&gt;
&lt;li&gt;A separate cookie and cache storage per profile, with no cross-writes&lt;/li&gt;
&lt;li&gt;Startup isolation so a leaked WebRTC handshake in profile A cannot expose profile B&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Standard browsers do not offer this. Antidetect browsers do. For a working setup on desktop, &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; is the option most agencies pair with residential proxies for social media work. Each profile ships a full Chromium environment with its own fingerprint, its own proxy binding, and its own isolated storage layer. The free tier includes 10 profiles, which covers most operators starting out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desktop Setup: One Fingerprint Per Threads Account
&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%2Fmdn1jgy6qemrtd9ncfpt.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%2Fmdn1jgy6qemrtd9ncfpt.png" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Threads has a working web version at threads.net, and for account management, the web is easier to script and audit than mobile. Here is the desktop workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create the profile in your antidetect browser.&lt;/strong&gt; Set the OS to Windows 10 or Windows 11, screen resolution to 1920x1080, and language to the target account's country. Match the timezone to the proxy exit country. If the proxy exits in Manila, set Asia/Manila, not UTC.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bind a residential proxy at the profile level.&lt;/strong&gt; Do not use a browser extension. The proxy must be attached to the profile itself so that DNS requests, WebRTC, and background sync all route through it. For Threads specifically, mobile 4G proxies score better than datacenter, and clean residential ASNs score better than commercial VPN ranges.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warm the profile for 48 hours before logging in.&lt;/strong&gt; Visit 20 to 30 unrelated sites, watch a few YouTube videos, search for random queries. This builds a plausible cookie history and gives the profile a background before Meta touches it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Log into Instagram first, then Threads.&lt;/strong&gt; Threads reads the Instagram session on join. If Instagram already trusts the profile, Threads inherits that trust. Skipping this step is why fresh Threads accounts get held for verification in the first 30 minutes.&lt;/p&gt;

&lt;p&gt;The same principle applies to any Meta-owned surface. The &lt;a href="https://dev.to/digitalgrowthpro/setting-up-isolated-browser-environments-for-e-commerce-operations-310a"&gt;isolated browser environment pattern for e-commerce&lt;/a&gt; uses the exact same layer stack, just with Shopify and Amazon on top instead of Meta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile Setup: When You Need Real Devices
&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%2F28l28lxt55hvjp51h5gl.jpg" 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%2F28l28lxt55hvjp51h5gl.jpg" alt=" " width="800" height="696"&gt;&lt;/a&gt;&lt;br&gt;
The Threads mobile app collects more signals than the web version. It reads GPS, cell tower ID, battery health, sensor calibration, and IMEI on Android or a device advertising ID on iOS. Emulators fake most of this, badly, and the app now flags emulator signatures within the first session.&lt;/p&gt;

&lt;p&gt;Real devices work, but 10 real phones cost more than most operators want to spend, and they all need SIMs. This is where cloud phones handle the load.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;cloud phone&lt;/a&gt; is a full Android instance running on a remote server, with its own IMEI, its own device model, its own SIM identity, and its own IP. You control it from your desktop. Each phone reads to Meta as a separate hardware device, which is the point.&lt;/p&gt;

&lt;p&gt;BitCloudPhone runs on ARM hardware, so app compatibility matches a physical phone, not an emulator. For a small setup, you can run five or ten cloud phones, install the Threads app on each, and treat each phone as one Threads account. Proxy per phone, timezone per phone, language per phone.&lt;/p&gt;

&lt;p&gt;For iOS-only workflows, the picture used to be worse. Cloud Android has been mature for two years, but cloud iOS did not really exist at consumer prices until early 2026. That changed with &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt;, which currently runs a real cloud iPhone with a per-device IDFA, GPS spoofing, and app store access. For managing Threads accounts that were originally created on iPhones, or for any operator running dating apps, Snapchat, or BeReal alongside Threads, iOS cloud is the piece that used to be missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proxy Pairing Rules That Actually Matter
&lt;/h2&gt;

&lt;p&gt;Proxies are the layer most operators get wrong. Three rules keep Threads from linking accounts on the network layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One proxy per account, sticky for the whole session.&lt;/strong&gt; Rotating IPs mid-session is a red flag on Meta. Sticky residential sessions of 10 to 30 minutes work better than round-robin.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Match the proxy country to the phone number, timezone, and language.&lt;/strong&gt; A US phone number logging in from a Manila IP is a linkage signal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never share a proxy between accounts.&lt;/strong&gt; Even a /24 subnet overlap is enough for Meta to group two accounts together.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For proxy type: 4G mobile proxies score highest for consumer social, residential is fine for most accounts, ISP proxies work if you can find clean ones, and datacenter proxies are cooked for anything Meta-owned. Budget 3 to 8 USD per account per month for proxies if you want them to hold.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warm-Up Workflow for New Threads Accounts
&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%2Fpyw0ut17172p6e8jet88.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%2Fpyw0ut17172p6e8jet88.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
A fresh Threads account posting five links on day one is the fastest way to trigger a hold. A better rhythm looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Days 1 to 3:&lt;/strong&gt; Log in, scroll the feed for 5 minutes per session, twice per day. No posts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 4 to 7:&lt;/strong&gt; Like 3 to 5 posts per session. Follow 2 to 3 accounts per day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Days 8 to 14:&lt;/strong&gt; Reply to one thread per day. Still no original posts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day 15 onward:&lt;/strong&gt; One original post per day, building toward 3 to 4 per day by day 30.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This mirrors how a new user behaves in the wild, and Meta's models are tuned against that baseline. Accounts that skip warm-up have a survival rate below 20 percent past 30 days, based on operator reports on public forums.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Mistakes That Get Threads Accounts Linked
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Reusing the same recovery email domain.&lt;/strong&gt; Ten accounts on ten fresh Gmail addresses is fine. Ten accounts all forwarding to the same catch-all domain is a graph edge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copying post content across accounts.&lt;/strong&gt; Meta hashes post text. Two accounts posting the same phrase within 24 hours is a strong linkage signal, even across separate fingerprints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging in from your real phone once.&lt;/strong&gt; A single session from your personal device attaches your account to your real device graph. It stays there. This is why the same principle applies to any &lt;a href="https://dev.to/digitalgrowthpro/how-to-manage-multiple-e-commerce-stores-without-account-association-in-2025-4gnj"&gt;multi-store or multi-account operation where account association is the failure mode&lt;/a&gt;, whether the platform is Threads, Amazon, or Shopify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;If you are running fewer than 5 Threads accounts, a desktop antidetect browser paired with residential proxies is enough. Once you cross 10 accounts, or once you need the mobile app for features like DM and camera, cloud phones become the cheaper option per account.&lt;/p&gt;

&lt;p&gt;Set up two profiles first, run them for a week, check that they do not cross-link. Then scale. Skipping the audit step is how operators lose 20 accounts to a single fingerprint leak on day one.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>socialmedia</category>
      <category>security</category>
    </item>
    <item>
      <title>Scripting Multi-Account Cash App Sessions on iOS Cloud with Appium</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Sun, 26 Jul 2026 23:14:22 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/scripting-multi-account-cash-app-sessions-on-ios-cloud-with-appium-c2a</link>
      <guid>https://dev.to/digitalgrowthpro/scripting-multi-account-cash-app-sessions-on-ios-cloud-with-appium-c2a</guid>
      <description>&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%2Fo3lg0qewnsxx15fk9uta.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%2Fo3lg0qewnsxx15fk9uta.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Cash App runs one of the strictest device attestation stacks among US fintech apps. Since the 2026 iOS 18 rollout, its client checks DeviceCheck, App Attest, and a set of hardware entropy signals before a session is trusted. Any test environment that fails those checks gets throttled inside 30 seconds, and a full ban within two sessions.&lt;/p&gt;

&lt;p&gt;For QA engineers, growth teams, and fintech researchers who need to script multiple parallel Cash App sessions, that reality has one practical answer: real iOS hardware in the cloud, driven by Appium, with each session pinned to its own network identity. Emulators do not pass App Attest. Jailbroken devices flag on the first request. The setup below is what actually holds up.&lt;/p&gt;

&lt;p&gt;This walkthrough covers the full stack: iOS cloud phone provisioning, Appium session isolation, network binding, and orchestration for 10+ concurrent sessions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Cash App breaks emulator-based automation
&lt;/h2&gt;

&lt;p&gt;Three signals make iOS emulators unusable for Cash App session work in 2026:&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%2F3e5nzg86g90485z7m41w.jpg" 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%2F3e5nzg86g90485z7m41w.jpg" alt=" " width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;DeviceCheck bit persistence.&lt;/strong&gt; Apple's DeviceCheck API stores two persistent bits tied to the physical Secure Enclave. Emulators simulate the API surface but return null or predictable values. Cash App's client fingerprints the response shape and blocks non-real devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App Attest hardware attestation.&lt;/strong&gt; iOS 14 introduced App Attest, which asks the Secure Enclave to sign a challenge with a key that never leaves the chip. No emulator can produce a valid signature. Cash App made attestation mandatory in April 2026.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sensor and motion entropy.&lt;/strong&gt; The Cash App onboarding flow reads accelerometer, gyroscope, and ambient light data during PIN setup. Emulator sensors return zeroed or looped values. That pattern is now a hard-flag signal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The only path around this is real iPhone hardware. Owning 10 iPhones for parallel testing costs $8,000+ and creates a network and provisioning headache. Cloud-hosted iOS devices remove both problems.&lt;/p&gt;
&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;Five components make this setup work.&lt;/p&gt;

&lt;p&gt;Device layer: &lt;a href="https://www.bitbrowser.net/cloudphone-ios" rel="noopener noreferrer"&gt;BitCloudPhone iOS&lt;/a&gt; provides real iPhone hardware in the cloud, one device per session, accessed by USBMuxd tunnel. Each device has its own IDFV, keychain, and Secure Enclave.&lt;/p&gt;

&lt;p&gt;Automation layer: Appium 2.5 with the XCUITest driver drives every iOS session. Network layer: a residential or mobile proxy per device, bound at the OS level so every app request egresses through the assigned IP. State layer: Redis or SQLite tracks session state and credential rotation. Orchestration layer: Python 3.11 ties it together, though Node.js and Ruby work identically.&lt;/p&gt;

&lt;p&gt;For the browser-side layer (Cash App's web dashboard, business.cash.app pages, and any adjacent OAuth flows), a fingerprint-isolated browser profile is bound to the same proxy. &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; handles that side because its profiles pair cleanly with the iOS device fingerprint (matching timezone, language, WebGL vendor). If you have not paired antidetect browsers with mobile automation before, this dev.to piece on &lt;a href="https://dev.to/digitalgrowthpro/cloud-phone-vs-android-emulator-which-one-actually-keeps-your-social-media-accounts-safe-136f"&gt;cloud phones vs Android emulators&lt;/a&gt; explains the tradeoff clearly.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1: Provision iOS cloud devices
&lt;/h2&gt;

&lt;p&gt;BitCloudPhone iOS exposes a REST API for device allocation. Each device returns a WebDriverAgent (WDA) endpoint that Appium connects to directly.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;BCP_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.bitbrowser.net/cloudphone/v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allocate_ios_device&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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;BCP_API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/devices/allocate&lt;/span&gt;&lt;span class="sh"&gt;"&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="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;API_KEY&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;os&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;ios&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;os_version&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;18.2&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;device_model&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;iPhone 14&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;region&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;region&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="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;residential&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;host&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;gate.smartproxy.com&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;port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&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="s"&gt;user-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_id&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;pass&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;your_proxy_pass&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;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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&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;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;allocate_ios_device&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cashapp_session_01&lt;/span&gt;&lt;span class="sh"&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;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wda_url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# ws://us-west-2.bitcloudphone.net:8443/wda/abc123
&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;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;udid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;       &lt;span class="c1"&gt;# 00008110-000A1D8C2ED8801E
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a signed WebDriverAgent URL, the device UDID, and a bundle identifier for the pre-installed Cash App build. Each session runs on a different physical device with a unique UDID, IDFV, and network path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Bind Appium to the cloud device
&lt;/h2&gt;

&lt;p&gt;Appium's XCUITest driver accepts a remote WDA URL directly. That skips local Xcode dependency entirely.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;appium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;appium.options.ios&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;XCUITestOptions&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_ios_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;XCUITestOptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform_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;iOS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;18.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;device_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;iPhone 14&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;udid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;udid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bundle_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.squareup.cash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;use_new_wda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;webdriver_agent_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wda_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;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_reset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auto_accept_alerts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wda_startup_retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
    &lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wda_launch_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60000&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;command_executor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4723&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_ios_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;no_reset=False&lt;/code&gt; wipes app state between sessions so credentials, cookies, and analytics identifiers don't bleed across accounts. This matters because Cash App reads NSUserDefaults and the keychain during launch, and any leftover token is a cross-account link signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Network isolation at the OS level
&lt;/h2&gt;

&lt;p&gt;App-level proxy config is not enough. Cash App uses &lt;code&gt;NSURLSession&lt;/code&gt; with pinned certificates on some endpoints and native sockets on others. If only the WebView traffic proxies, you leak the real device IP on the socket calls.&lt;/p&gt;

&lt;p&gt;BitCloudPhone iOS sets the proxy at the packet layer, so every process on the device (Cash App included) exits through the assigned residential or mobile IP. You confirm it with a quick attestation call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_egress_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected_ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobile: launchApp&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bundleId&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;com.apple.mobilesafari&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;driver&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;https://api.ipify.org?format=json&lt;/span&gt;&lt;span class="sh"&gt;"&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;xpath&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;//XCUIElementTypeStaticText&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;actual&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;split&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;ip&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;body&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;actual&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected_ip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;verify_egress_ip&lt;/code&gt; returns False, discard the device and reallocate. Session pollution is the single biggest cause of Cash App account limits in automated testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Multi-session orchestration
&lt;/h2&gt;

&lt;p&gt;For 10 parallel Cash App sessions, use asyncio with a semaphore so device allocation and Appium spin-up don't race. Each task owns one device end to end.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;

&lt;span class="n"&gt;MAX_PARALLEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_session&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;sem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;loop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;allocate_ios_device&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="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;build_ios_session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;run_test_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&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="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;loop&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_in_executor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;release_device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;udid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;async&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;profiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="n"&gt;sem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Semaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MAX_PARALLEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sem&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;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cashapp_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;03&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;release_device&lt;/code&gt; calls the BitCloudPhone API to reset the device, wipe the keychain, roll the IDFV, and requeue it for the next session. Reuse without reset is another common link failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fingerprint parity between iOS device and browser
&lt;/h2&gt;

&lt;p&gt;When a session touches the Cash App web dashboard (payment reconciliation, statement export), the browser fingerprint must line up with the device fingerprint. Timezone, language, GPU vendor, and IP geolocation should all match.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i"&gt;BitBrowser + Selenium walkthrough&lt;/a&gt; covers browser-side setup. The key detail for Cash App parity: pull the device locale from the iOS session and mirror it into the BitBrowser profile before launching Selenium.&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="n"&gt;device_locale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobile: deviceInfo&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;locale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;device_timezone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mobile: deviceInfo&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;timeZone&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_profile&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;name&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="s"&gt;web_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile_id&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;os&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;MacIntel&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;timezone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device_timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device_locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# same egress IP as the phone
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same IP, same timezone, same language, and Cash App's risk model sees the web session as the same customer as the mobile session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common pitfalls
&lt;/h2&gt;

&lt;p&gt;Four failure modes catch teams new to iOS cloud automation.&lt;/p&gt;

&lt;p&gt;The first is WDA session leaks. If Appium crashes mid-test, the WebDriverAgent process on the device stays running. Always call &lt;code&gt;driver.quit()&lt;/code&gt; inside a &lt;code&gt;try/finally&lt;/code&gt;, and hit the BitCloudPhone &lt;code&gt;/devices/{udid}/reset&lt;/code&gt; endpoint on failure.&lt;/p&gt;

&lt;p&gt;Clock drift is the second. Cloud devices in different regions can drift by 200–800ms. Cash App's request signing rejects timestamps outside a 5-second window, so sync every device to NTP on allocation.&lt;/p&gt;

&lt;p&gt;Third is push notification bleed. APNs tokens survive app reinstall on iOS. Force-remove the app via the API, not through the Springboard automation, or you keep the old token and link two accounts to the same device history.&lt;/p&gt;

&lt;p&gt;Screenshot exfiltration is the fourth. Never save Cash App screenshots to shared storage. IDFA-adjacent metadata in the EXIF can link sessions across your test fleet.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Can I use Appium on iOS simulators for this?&lt;/strong&gt;&lt;br&gt;
No. Simulators do not have a Secure Enclave and cannot pass App Attest. Cash App rejects the session before the login screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many concurrent iOS cloud devices can Appium handle?&lt;/strong&gt;&lt;br&gt;
Appium 2.x scales linearly. The bottleneck is your test host CPU (each WDA session uses ~150MB RAM and one core burst on start). A 16-core box comfortably runs 32 sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a jailbroken iPhone?&lt;/strong&gt;&lt;br&gt;
No, and you should not use one. Cash App's client checks for common jailbreak signatures (Cydia paths, sandbox escape indicators) and bans within seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is BitCloudPhone iOS the only cloud iPhone provider that works?&lt;/strong&gt;&lt;br&gt;
Several providers exist. BitCloudPhone iOS pairs directly with BitBrowser profiles for fingerprint parity, which is why the setup here uses it. GeeLark and NSTBrowser have iOS options too, though the browser-side parity requires extra work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What proxy type gives the highest session stability?&lt;/strong&gt;&lt;br&gt;
Residential proxies with sticky sessions (10-minute rotation) work for read-heavy flows. For send/receive flows, 4G mobile proxies match the network profile Cash App expects from a real user and rarely trigger step-up verification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to build next
&lt;/h2&gt;

&lt;p&gt;The setup above handles session scripting. The layer worth adding on top is a state store (Redis or SQLite) that tracks device-to-profile-to-proxy pairings across runs. Reuse the same triplet on the same account every time, and Cash App's model treats it as a returning customer instead of a fresh device.&lt;/p&gt;

&lt;p&gt;If you script this end to end, you'll have a fintech test setup that survives App Attest, DeviceCheck, and every 2026 signal Cash App currently ships. That's a rare thing to own in the iOS automation space.&lt;/p&gt;

&lt;p&gt;Discussion open in the comments — what's the largest concurrent iOS session count you've run against a fintech app, and which signal broke first?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How Websites Know You're Using Multiple Accounts (Even With a Proxy)</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Thu, 16 Jul 2026 13:57:50 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/how-websites-know-youre-using-multiple-accounts-even-with-a-proxy-5cc9</link>
      <guid>https://dev.to/digitalgrowthpro/how-websites-know-youre-using-multiple-accounts-even-with-a-proxy-5cc9</guid>
      <description>&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%2Frd58kicbqsvchls57sl6.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%2Frd58kicbqsvchls57sl6.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
A store owner in 2025 opened five Amazon seller accounts, put each one behind a different proxy, and got all five suspended in the same week. The proxies worked fine. The IPs were clean. Amazon still linked every account back to him.&lt;/p&gt;

&lt;p&gt;This happens more than people expect, and the reason is simple once you see it: a proxy only changes one thing about you online. Your IP address. Everything else your browser hands over stays exactly the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proxy hides your address, not your identity
&lt;/h2&gt;

&lt;p&gt;Think of a proxy like changing your house number but keeping the same car, the same walk, and the same handwriting on every letter you send. The platform doesn't need your street address to recognize you. It just needs enough small details that repeat.&lt;/p&gt;

&lt;p&gt;Websites collect dozens of these details every time you load a page: your screen resolution, your installed fonts, your time zone, the exact version of your graphics driver, how your browser renders a tiny hidden image, even the pattern of your mouse movements. None of that travels through a proxy. It comes straight from your browser and your device.&lt;/p&gt;

&lt;p&gt;Put those details together and you get something researchers call a fingerprint. Individually, a screen resolution of 1920x1080 means nothing, thousands of people share it. But stack twenty or thirty of these signals and the combination becomes almost as unique as an actual fingerprint. Studies from privacy research groups have found that these combinations can identify a single device with well over 90% accuracy, cookies or no cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually gets checked
&lt;/h2&gt;

&lt;p&gt;A few examples make this less abstract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canvas and WebGL rendering.&lt;/strong&gt; Your browser draws invisible test graphics in the background, and the exact pixels it produces depend on your specific GPU and drivers. Two people with the same laptop model from the same store can still produce slightly different results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio processing.&lt;/strong&gt; Browsers can run a silent sound wave through your audio hardware and read back the output. The floating-point values that come out are shaped by your sound card and drivers, so they act as another marker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLS handshake data.&lt;/strong&gt; Before your browser even loads a page, it negotiates an encrypted connection with the server. The order and combination of settings it sends during that handshake, known in security circles as a JA3 fingerprint, is consistent enough that a server can identify your browser type without reading a single cookie or asking for your IP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Behavior patterns.&lt;/strong&gt; How fast you type, how you scroll, whether your mouse moves in straight lines or natural curves. Automation tools tend to move in ways real hands don't.&lt;/p&gt;

&lt;p&gt;None of these depend on your IP. That's why five accounts behind five different proxies can still get flagged as the same person the moment the fingerprints line up.&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%2Fjrxbcutpp2jyzcv84ryw.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%2Fjrxbcutpp2jyzcv84ryw.webp" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake most people make next
&lt;/h2&gt;

&lt;p&gt;Once someone learns about fingerprinting, the instinct is to fight it by changing everything constantly. New user agent every session. Randomized screen size. A different set of fonts each time you log in.&lt;/p&gt;

&lt;p&gt;This tends to backfire, and 2026 detection systems are part of the reason why. Platforms don't just look at whether your fingerprint matches a real device, they check whether your fingerprint is even &lt;em&gt;possible&lt;/em&gt;. A screen resolution that doesn't match your reported GPU, or fonts that don't exist together on any real operating system, reads as more suspicious than a normal fingerprint that simply repeats.&lt;/p&gt;

&lt;p&gt;Detection has also moved from checking single signals to checking whether your identity stays stable over time. A profile that looks slightly different every single session raises more flags than one that looks the same, because real users don't reinstall their operating system every morning. Constant randomization is often what gets an account reviewed in the first place.&lt;/p&gt;

&lt;p&gt;The safer approach is the opposite of what most guides suggest: build one clean, consistent, realistic profile per account, and keep it stable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a stable setup actually looks like
&lt;/h2&gt;

&lt;p&gt;Three things matter more than anything else:&lt;/p&gt;

&lt;p&gt;Each account needs its own separate browser environment, not just a different tab or a different proxy on the same browser. Cookies, local storage, and cached fingerprint data need to stay fully isolated between accounts, or platforms will connect them through shared session data even with different IPs.&lt;/p&gt;

&lt;p&gt;The IP and the fingerprint need to match. An IP that geolocates to Germany paired with a system clock set to a US time zone and English-only fonts is a mismatch that fraud systems are specifically built to catch.&lt;/p&gt;

&lt;p&gt;And the profile needs to stay the same across sessions. Same screen resolution, same fonts, same hardware signature, every time that account logs in.&lt;/p&gt;

&lt;p&gt;This is the exact problem antidetect browsers were built to solve. Tools in this category matter for teams managing multiple accounts, multiple brands, or multiple client environments online, because platforms may still connect environments that look too similar even when there's a legitimate reason to keep them separate. &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser &lt;/a&gt;handles this by giving each profile its own isolated fingerprint, cookies, and local storage, so one account never leaks data into another. It also keeps that profile's hardware signature stable across sessions instead of randomizing it, which lines up with how modern detection actually works. For teams running proxies alongside it, matching the proxy's location to the profile's fingerprint settings (time zone, language, fonts) takes a few minutes and closes most of the obvious mismatches on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mobile accounts have their own version of this problem
&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%2F6pjx541wqyxq0u4m9549.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%2F6pjx541wqyxq0u4m9549.png" alt=" " width="800" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything above focuses on desktop browsers, but the same logic applies to phones. If you're managing accounts on Instagram, TikTok, or WhatsApp from an emulator or a rooted device, apps can read hardware IDs, sensor data, and even how the device physically shakes while sitting on a desk, since a real phone in someone's pocket moves differently than one bolted to a server rack. Emulators tend to report suspiciously perfect, identical values across every instance, which is its own kind of tell.&lt;/p&gt;

&lt;p&gt;This is where cloud phones come in, real or virtualized Android devices running remotely, each with its own IMEI, sensor data, and hardware profile instead of one emulator cloned fifty times. &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitCloudPhone &lt;/a&gt;works this way for teams running social accounts at scale, since each phone instance behaves like a distinct physical device rather than a copy-pasted emulator, which matters a lot on platforms like TikTok that lean heavily on device-level signals rather than just browser fingerprints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a "clean" fingerprint isn't automatically a safe one
&lt;/h2&gt;

&lt;p&gt;There's a trap here worth mentioning. A brand-new browser profile with no history, no cookies, and no past activity can look just as suspicious as a mismatched one. Fraud systems have learned that real accounts build up a trail over time: saved logins, browsing history, small inconsistencies that come from actual use. A profile that's spotless in every category can read as manufactured, especially on a platform that already flagged similar accounts before.&lt;/p&gt;

&lt;p&gt;That's part of why isolation alone doesn't solve everything. A profile needs to accumulate a normal-looking history the same way a real device would, rather than showing up polished and empty right before it starts placing orders or posting content.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short checklist before you scale up
&lt;/h2&gt;

&lt;p&gt;Before adding a sixth or tenth account, it helps to check a few basics. Is each profile isolated from the others, with no shared cookies or cached data? Does the IP location match the fingerprint's time zone and language? Is the profile staying consistent across logins instead of changing every time? And is the browsing behavior on that account realistic, meaning normal pauses, normal scrolling, nothing that reads like a script running on autopilot?&lt;/p&gt;

&lt;p&gt;Most bans that get blamed on "bad proxies" trace back to one of these four points, not the proxy itself. A residential IP with a mismatched, constantly shifting fingerprint gets caught. A cheap datacenter IP with a clean, consistent, well-matched profile often survives far longer than people expect.&lt;/p&gt;

&lt;p&gt;The proxy was never the whole job. It was one piece of a much larger picture, and platforms have gotten good at reading the rest of it.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>7 Fingerprint Test Tools Every Multi-Account Operator Should Run Weekly</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Mon, 22 Jun 2026 10:42:55 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/7-fingerprint-test-tools-every-multi-account-operator-should-run-weekly-4h71</link>
      <guid>https://dev.to/digitalgrowthpro/7-fingerprint-test-tools-every-multi-account-operator-should-run-weekly-4h71</guid>
      <description>&lt;p&gt;A profile that passed every check last Tuesday can fail on Friday. That's not an exaggeration. Cloudflare shipped over 25 detection-rule updates in a 10-month window ending Q1 2026, according to Zyte's Web Scraping Industry Report. Other anti-fraud vendors are on similar cycles. If you run multiple browser profiles for client work, ad verification, e-commerce, or affiliate campaigns, testing once during initial setup is the same as not testing at all.&lt;/p&gt;

&lt;p&gt;I run weekly checks across all active profiles. The routine takes about 40 minutes for a fleet of 30 profiles, and it has caught drift that would have cost me accounts more times than I want to admit. Here are the seven tools I use, what each one actually tests, where it's weak, and how to read the results.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. CreepJS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://abrahamjuliot.github.io/creepjs/" rel="noopener noreferrer"&gt;https://abrahamjuliot.github.io/creepjs/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CreepJS doesn't just fingerprint you. It checks whether your fingerprint is &lt;em&gt;honest&lt;/em&gt;. Most testing tools ask "what does your browser report?" CreepJS asks "is your browser lying about what it reports?" That distinction matters because detection teams at Meta, Amazon, and Google run the same kind of consistency analysis internally.&lt;/p&gt;

&lt;p&gt;The tool collects your canvas hash, WebGL renderer, audio context, fonts, screen properties, and dozens more signals, then cross-references them. A profile that claims to be Windows 11 with an Intel GPU but leaks a macOS-style canvas hash will get flagged red. CreepJS calls this a "lies" score.&lt;/p&gt;

&lt;p&gt;Run it on every new profile before you do anything else. If CreepJS marks inconsistencies, the profile isn't ready, period. No point checking anything downstream until this one is clean.&lt;/p&gt;

&lt;p&gt;One limitation: CreepJS won't tell you about your IP reputation or proxy quality. It's browser-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pixelscan
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://pixelscan.net/" rel="noopener noreferrer"&gt;https://pixelscan.net/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pixelscan has become the go-to consistency checker in the antidetect community, and there's a good reason. Its rule engine is specifically tuned to catch the mismatches that platforms like Facebook, Amazon, and Google flag. Canvas, WebGL, audio, fonts, timezone, language, screen resolution, user-agent, all compared against each other and against your reported OS/browser combination.&lt;/p&gt;

&lt;p&gt;A green result means your profile's internal signals are consistent with each other. That's a baseline, not a guarantee. I've had profiles pass Pixelscan and still trip behavioral detection on Meta within two days. So green means "fingerprint is technically coherent" and nothing more.&lt;/p&gt;

&lt;p&gt;What Pixelscan does better than CreepJS: it gives you a single pass/fail per category with a color-coded dashboard, so a quick visual scan across 30 profiles takes minutes. What it does worse: it's less aggressive at detecting spoofing compared to CreepJS's "lies" analysis.&lt;/p&gt;

&lt;p&gt;I run both. They catch different things.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. IPHey
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://iphey.com/" rel="noopener noreferrer"&gt;https://iphey.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IPHey focuses on the network side. While Pixelscan and CreepJS care about your browser fingerprint, IPHey digs into your IP address, proxy detection, DNS configuration, geographic consistency, and ASN reputation.&lt;/p&gt;

&lt;p&gt;The tool checks whether your IP is flagged on known blacklists, whether it looks like a datacenter address or a residential one, and whether your DNS servers match your supposed location. A profile with a British IP but DNS resolving through a German server is a problem. IPHey catches that.&lt;/p&gt;

&lt;p&gt;I pair every Pixelscan check with an IPHey check. It's not unusual for a profile to show green on Pixelscan (the browser fingerprint is clean) but red on IPHey (the proxy is burned or geographically inconsistent). Both need to pass.&lt;/p&gt;

&lt;p&gt;Residential proxies from providers like IPRoyal or Proxy-Seller typically score much better here than datacenter IPs, though even residential IPs can degrade over time as they end up on blacklists.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. BrowserLeaks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://browserleaks.com/" rel="noopener noreferrer"&gt;https://browserleaks.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BrowserLeaks has been around for years, and it's still one of the most detailed single-page diagnostic tools available. It breaks testing into individual modules: canvas, WebGL, fonts, CSS, WebRTC, geolocation, JavaScript, and content filters.&lt;/p&gt;

&lt;p&gt;The WebRTC test alone is worth bookmarking. A misconfigured profile might pass fingerprint checks but leak your real IP through WebRTC. Most antidetect browsers disable or spoof WebRTC by default, but I've seen configurations where a browser update reset the WebRTC setting silently. Weekly checks catch that.&lt;/p&gt;

&lt;p&gt;BrowserLeaks is also the best tool on this list for font-enumeration testing. Fonts are an underrated fingerprinting signal. A profile claiming to run on a stock Windows 11 install shouldn't have macOS-exclusive fonts showing up. BrowserLeaks lists every detectable font, so you can spot anomalies that other tools miss.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. AmIUnique
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://amiunique.org/" rel="noopener noreferrer"&gt;https://amiunique.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AmIUnique is a research project from INRIA that maintains a database of over 2 million collected fingerprints. Its main value: it tells you how unique your profile looks compared to that dataset.&lt;/p&gt;

&lt;p&gt;If your fingerprint appears in 0.01% of the database, you stand out. If it matches 15% of users, you blend in. For multi-account work, blending in is the goal. You want your profiles to look like ordinary Chrome-on-Windows users, not like rare configurations that draw attention.&lt;/p&gt;

&lt;p&gt;I check AmIUnique monthly rather than weekly (the uniqueness score doesn't shift as fast as detection rules do). But after any major configuration change (swapping proxy type, updating the browser core, changing the OS profile), I recheck immediately.&lt;/p&gt;

&lt;p&gt;The catch: AmIUnique's database skews toward privacy-conscious European users, so a profile mimicking a regular user from Southeast Asia may score as "unique" simply because that demographic is underrepresented in the sample. Factor that in.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Coveryourtracks (EFF)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://coveryourtracks.eff.org/" rel="noopener noreferrer"&gt;https://coveryourtracks.eff.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Electronic Frontier Foundation built this tool (formerly called Panopticlick) to educate people about browser tracking. For operators, it's useful because it tests ad-blocker and tracker-blocker fingerprinting, a signal category that the other tools on this list mostly ignore.&lt;/p&gt;

&lt;p&gt;Here's why that matters: your tracking protection settings are themselves a fingerprinting vector. A profile that blocks all third-party cookies, uses a specific ad-blocker, and has a particular Do-Not-Track header is identifiable by that exact combination of protections. If all your profiles have the same unusual tracking-protection configuration, they're linkable.&lt;/p&gt;

&lt;p&gt;Coveryourtracks shows you exactly which tracking protections are active and how unique that combination is. I use it to make sure my profiles don't all share an identical protection signature.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Fingerprint.com Bot Detection Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;URL:&lt;/strong&gt; &lt;a href="https://fingerprint.com/products/bot-detection/" rel="noopener noreferrer"&gt;https://fingerprint.com/products/bot-detection/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fingerprint.com (formerly FingerprintJS) is the company behind the commercial detection engine used by several major platforms. Their public demo page runs a subset of the same analysis their paying clients use.&lt;/p&gt;

&lt;p&gt;The demo checks for signs of automation: headless browser markers, Selenium/Puppeteer/Playwright traces, CDP (Chrome DevTools Protocol) leaks, and behavioral anomalies. Passing this test doesn't mean you'll pass the full commercial product, but failing it means you have a serious problem.&lt;/p&gt;

&lt;p&gt;This is where your choice of antidetect browser matters most. Some antidetect tools are glorified Chromium wrappers that still leak CDP signals. &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; patches these at the engine level, which is why it tends to pass bot-detection demos that trip up simpler tools. I've tested this side-by-side with three other browsers, and the gap shows up specifically in the automation-detection category.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Weekly Routine I Actually Follow
&lt;/h2&gt;

&lt;p&gt;I don't run all seven tools on all 30 profiles. That would take hours. Here's my workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every profile, weekly:&lt;/strong&gt; Pixelscan + IPHey. Two tabs, 30 seconds per profile. If either shows a new red flag, I investigate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Random sample of 5 profiles, weekly:&lt;/strong&gt; CreepJS full scan. CreepJS is slower and requires more interpretation, so I rotate through the fleet over a few weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After any change:&lt;/strong&gt; If I swap a proxy, update the browser, or adjust a fingerprint setting, that specific profile gets the full seven-tool pass before it goes back into active use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monthly:&lt;/strong&gt; AmIUnique uniqueness check on a representative sample. Coveryourtracks on one profile per OS type to verify tracking-protection consistency.&lt;/p&gt;

&lt;p&gt;The entire routine fits inside Monday morning before I start actual work. It's also faster when you're using a browser that offers API-driven profile management — I launch profiles through &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser's&lt;/a&gt; local API, which means I can script the testing sequence rather than clicking through a GUI 30 times.&lt;/p&gt;

&lt;p&gt;For profiles that manage mobile-first platforms (TikTok, Instagram, Snapchat), a desktop antidetect browser alone won't cut it. These platforms check mobile-specific signals like IMEI, device model, and screen density. &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; handles the mobile environment side — I run the same fingerprint testing suite through the cloud phone's built-in browser to verify consistency there, too.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Testing Doesn't Cover
&lt;/h2&gt;

&lt;p&gt;Fingerprint testing tells you whether your profiles are technically clean. It can't save you from behavioral detection. Logging into 10 accounts from the same subnet in a 2-minute window, copy-pasting the same message across profiles, or following identical navigation patterns — those trigger statistical analysis that no fingerprint score can prevent.&lt;/p&gt;

&lt;p&gt;Testing is the floor, not the ceiling. If your profiles are dirty on Pixelscan, nothing else matters. If they're clean, you still need to operate them like separate humans would.&lt;/p&gt;

&lt;p&gt;Running this routine since January 2025 has kept my ban rate under 3% across platforms. Before I started weekly testing, I was losing 2-3 profiles per month to silent association bans. Forty minutes a week is a bargain for that kind of risk reduction.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Captcha-Solving Integration Patterns for Automated Multi-Profile Sessions</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Wed, 17 Jun 2026 19:25:15 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/captcha-solving-integration-patterns-for-automated-multi-profile-sessions-48l4</link>
      <guid>https://dev.to/digitalgrowthpro/captcha-solving-integration-patterns-for-automated-multi-profile-sessions-48l4</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fmz2dqr6g018wtb6fh7hy.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.amazonaws.com%2Fuploads%2Farticles%2Fmz2dqr6g018wtb6fh7hy.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roughly 40% of all web traffic in 2026 gets intercepted by a captcha at least once per session. For anyone running automated browser profiles (QA engineers testing geo-targeted flows, affiliate managers scaling campaigns, or freelancers operating client dashboards), that 40% translates directly into broken workflows and stalled pipelines.&lt;/p&gt;

&lt;p&gt;I've spent the last eight months building captcha-handling layers into multi-profile browser setups for a small agency. What follows is the architecture we landed on, the tradeoffs between the three main solver categories, and the code patterns that actually held up past the first week.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where captchas show up in multi-profile work
&lt;/h2&gt;

&lt;p&gt;A single browser profile browsing a single site rarely triggers aggressive captcha challenges. The problems multiply when you're operating 10, 30, or 80 profiles in parallel. Here's what actually triggers them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IP reputation scoring.&lt;/strong&gt; Sites cross-reference your IP against shared blocklists. Residential IPs score better than datacenter IPs, but even residential ranges trip captchas when multiple profiles share the same /24 subnet within a short window.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLS and HTTP/2 fingerprint mismatch.&lt;/strong&gt; Your browser claims to be Chrome 126, but the TLS ClientHello carries a JA4 hash that matches a headless Chromium build. The site doesn't block you outright. It just serves a reCAPTCHA v3 challenge scored at 0.1.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session velocity.&lt;/strong&gt; Loading a product page 0.8 seconds after opening a fresh profile signals automation. Real users fumble around, scroll back, read the cookie banner, and take 6-12 seconds before their first meaningful click.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Canvas/WebGL consistency checks.&lt;/strong&gt; If a platform detects that two profiles sharing a proxy return identical canvas hashes, both get flagged. Antidetect browsers like &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; handle this by generating per-profile hardware fingerprints, but if your captcha solver callback happens in a context that leaks a different fingerprint, the session still breaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three solver categories (and when each one fails)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. API-based third-party solvers
&lt;/h3&gt;

&lt;p&gt;Services like 2Captcha, Anti-Captcha, CapSolver, and CapMonster accept a captcha payload (image bytes, sitekey, page URL), farm it out to human workers or ML models, and return a token. Pricing sits around $1.50–3.00 per 1,000 reCAPTCHA v2 solves as of June 2026. hCaptcha costs more, roughly $2.80–4.50 per 1,000.&lt;/p&gt;

&lt;p&gt;The integration pattern is straightforward. You intercept the captcha element on the page, extract the sitekey and page URL, POST them to the solver API, poll for the result, then inject the response token into the callback function.&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="c1"&gt;// Pseudocode: works with Puppeteer, Playwright, or any CDP-based controller&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;siteKey&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;el&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.g-recaptcha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-sitekey&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="kc"&gt;null&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;taskId&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;solverClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTask&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RecaptchaV2TaskProxyless&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;websiteURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;websiteKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;siteKey&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;solution&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;solverClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&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;120000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;g-recaptcha-response&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Some sites bind to a callback function&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;___grecaptcha_cfg&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&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="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;___grecaptcha_cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clients&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;aa&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&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;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;solution&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gRecaptchaResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where this breaks:&lt;/strong&gt; reCAPTCHA v3 doesn't show a visible challenge. It scores behavior passively. A third-party token injected into a v3 flow often arrives with a score of 0.1–0.3 because the "solve" didn't include any organic mouse movement or scroll data. Google's scoring API sees a token appear with zero behavioral history and quietly flags it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Browser-extension solvers
&lt;/h3&gt;

&lt;p&gt;Extensions like Buster or NopeCHA run inside the browser process itself. They attempt audio or visual challenge solving locally, without sending data to a third party. The advantage: they produce tokens that carry the same session context as the browser profile, so v3 behavioral scores are slightly higher.&lt;/p&gt;

&lt;p&gt;The integration problem is different here. When you're managing 50 profiles, each profile needs the extension installed and configured independently. If you're using an antidetect browser that supports per-profile extension management (BitBrowser handles this through its profile API, for example), you can automate the installation. If not, you're manually loading unpacked extensions into each Chromium instance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# BitBrowser local API: attach an extension to a specific profile&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:54345/browser/update &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;'{
    "id": "profile_abc123",
    "extensions": ["/path/to/nopecha_unpacked"]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where this breaks:&lt;/strong&gt; Audio challenges. Google has steadily degraded audio captcha quality since late 2025. Buster's success rate on audio-based reCAPTCHA dropped from ~85% in early 2025 to somewhere around 40% by Q1 2026 in my testing. NopeCHA performs better on hCaptcha image challenges but still chokes on adversarial grids (the ones where a bus tire overlaps two squares).&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Session-warming and avoidance
&lt;/h3&gt;

&lt;p&gt;This isn't solving at all. It's structuring your automation so captchas don't appear. After months of patching solver failures, this ended up being the approach we use most.&lt;/p&gt;

&lt;p&gt;The idea: before your automation script runs its actual task, it "warms" the profile with organic-looking activity. Load the site's homepage. Wait 3-5 seconds. Scroll down. Click a navigation link. Wait again. Then start the real task. By the time the script hits the page that would normally show a captcha, the session already carries enough behavioral signal that reCAPTCHA v3 scores it above the site's threshold (usually 0.5 or 0.7).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;warm_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base_domain&lt;/span&gt; &lt;span class="o"&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;target_url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_domain&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&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;600&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&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="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;window.scrollBy(0, document.body.scrollHeight * 0.3)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Click a random internal link if available
&lt;/span&gt;    &lt;span class="n"&gt;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query_selector_all&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;a[href*=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;base_domain&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;links&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Where this breaks:&lt;/strong&gt; Sites with mandatory captcha gates (login forms, registration pages, checkout flows). No amount of session warming skips a hard reCAPTCHA v2 challenge on a login page. You need a solver for those.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it together: the hybrid pipeline
&lt;/h2&gt;

&lt;p&gt;The production setup we've been running since February 2026 combines all three approaches in a fallback chain.&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.amazonaws.com%2Fuploads%2Farticles%2Fshwdi4wvfdor9j2h6bme.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.amazonaws.com%2Fuploads%2Farticles%2Fshwdi4wvfdor9j2h6bme.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 1: Fingerprint consistency.&lt;/strong&gt; Each profile gets a unique hardware fingerprint and a dedicated residential proxy. We use BitBrowser for profile management because it exposes a local REST API that our orchestrator can call to create, launch, and connect to profiles programmatically. The &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;cloud phone feature&lt;/a&gt; covers mobile-app sessions where a desktop antidetect browser can't replicate the device environment accurately enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 2: Session warming.&lt;/strong&gt; Every profile runs a 15–30 second warm-up routine on the target domain before executing the real task. This alone eliminated about 70% of the reCAPTCHA v3 challenges we were hitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3: Extension-based solving.&lt;/strong&gt; For sites that still present a visual challenge, NopeCHA inside the profile attempts it first. Because it runs in-profile, the solve carries the correct session cookies and behavioral context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 4: API fallback.&lt;/strong&gt; If the extension fails twice on the same challenge, we extract the sitekey and POST it to CapSolver as a fallback. We chose CapSolver over 2Captcha after running 10,000 solves through each in March — CapSolver returned results about 12 seconds faster on average for reCAPTCHA v2, though 2Captcha had a marginally higher success rate on Turnstile challenges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Profile Launch → Warm Session → Task Execution
                                     ↓
                              Captcha Detected?
                              /             \
                           No                Yes
                           ↓                  ↓
                      Continue         Extension Solve
                                           ↓
                                      Solved? → Yes → Continue
                                           ↓
                                          No
                                           ↓
                                    API Fallback → Continue / Retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Cost math for freelancers
&lt;/h2&gt;

&lt;p&gt;This is where multi-profile captcha solving gets painful if you don't think about it early.&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.amazonaws.com%2Fuploads%2Farticles%2F35kzmt3tpryvwh306b71.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.amazonaws.com%2Fuploads%2Farticles%2F35kzmt3tpryvwh306b71.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Running 30 profiles with 5 tasks each per day, hitting captchas on 40% of tasks, means roughly 60 solver calls daily. At $2.50 per 1,000 solves via API, that's about $0.15/day or $4.50/month. Manageable.&lt;/p&gt;

&lt;p&gt;But scale to 200 profiles at 20 tasks each with a 60% captcha rate, and you're looking at 2,400 daily solves — $6/day, $180/month. Session warming dropping your captcha encounter rate from 60% to 15% saves you $135/month. That single optimization pays for a residential proxy subscription.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;If I started this project again, I'd skip the "solve everything with an API" phase entirely. We burned three weeks and about $200 in solver credits before realizing that fingerprint consistency and session warming eliminated most challenges before they appeared. The API solver is a safety net, not a primary strategy.&lt;/p&gt;

&lt;p&gt;I'd also invest earlier in monitoring. We now log every captcha encounter (type, URL, profile ID, solver used, result, latency) to a SQLite database. Patterns jump out fast: a specific proxy subnet triggering 3× more challenges than others, or a site rolling out Turnstile on a page that previously used reCAPTCHA. Without those logs, you're debugging blind.&lt;/p&gt;

&lt;p&gt;The tooling keeps shifting. Cloudflare's Turnstile gained significant market share this year, and its proof-of-work mechanism requires a different solving approach than Google's reCAPTCHA. Whatever pipeline you build, make the solver layer pluggable. You'll swap providers more often than you think.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>automation</category>
      <category>security</category>
      <category>python</category>
    </item>
    <item>
      <title>Building a Multi-Account Reddit Management Backend with BitBrowser API + Proxy Rotation</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Fri, 22 May 2026 20:06:14 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/building-a-multi-account-reddit-management-backend-with-bitbrowser-api-proxy-rotation-3nl</link>
      <guid>https://dev.to/digitalgrowthpro/building-a-multi-account-reddit-management-backend-with-bitbrowser-api-proxy-rotation-3nl</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2F9cmevz5zt99gnbcdw7iw.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.amazonaws.com%2Fuploads%2Farticles%2F9cmevz5zt99gnbcdw7iw.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
Reddit is a notoriously hostile environment for multi-account workflows, even for legitimate ones. Social media agencies managing brand presence across 10+ subreddits, market research teams running parallel survey threads, and community management platforms all hit the same wall: Reddit's anti-multi-account detection is aggressive, opaque, and unforgiving. A single shared IP across two work accounts can trigger a shadowban that takes weeks to discover.&lt;/p&gt;

&lt;p&gt;This article walks through the architecture of a multi-account Reddit management backend that handles isolation correctly. The stack uses BitBrowser's local API for browser-level identity separation, a residential proxy rotation layer, and a Node.js orchestration service. Detection evasion is not the goal here. What matters is making each account look like what it actually is: a separate human operator working from a consistent device and network.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why Reddit Is Harder Than Facebook or TikTok
&lt;/h2&gt;

&lt;p&gt;Most multi-account literature assumes Facebook-grade detection. Reddit operates differently. Three things make it harder:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shadowbans replace hard bans.&lt;/strong&gt; Your account looks active to you. Posts appear in your profile. Comments seem to land. But nobody else sees them. Detection of a shadowban often takes 2-3 weeks of wasted work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Account age weighs heavily.&lt;/strong&gt; A 3-month-old account posting in finance subreddits gets auto-filtered. The same account 18 months later, with consistent karma history, posts freely. Your backend has to track this and gate which accounts can do what.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-account signals are subtle.&lt;/strong&gt; Reddit fingerprints include browser canvas, timezone vs IP geolocation mismatch, posting cadence patterns, and even typing rhythm in newer detection updates.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For legitimate use cases (agency client management, research data collection, brand monitoring), this means the technical bar is high. The setup below is what we run in production for an agency handling 24 brand accounts across 12 industries.&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The system has four layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────┐
│  Orchestration Service (Node.js)    │
│  - Account state machine            │
│  - Task queue (BullMQ + Redis)      │
│  - Action scheduler                 │
└───────────────┬─────────────────────┘
                │
        ┌───────┴────────┐
        │                │
┌───────▼────────┐  ┌────▼─────────────┐
│ BitBrowser     │  │ Proxy Pool       │
│ Local API      │  │ Manager          │
│ (per-profile)  │  │ (sticky sessions)│
└───────┬────────┘  └──────┬───────────┘
        │                  │
        └────────┬─────────┘
                 │
       ┌─────────▼─────────┐
       │ Reddit (via API   │
       │ or browser-driven │
       │ Playwright)       │
       └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator never talks to Reddit directly. It dispatches actions to BitBrowser profiles, which route through assigned proxies. Each profile carries its own fingerprint, cookies, and session state, locked to one Reddit identity for its entire lifetime.&lt;/p&gt;

&lt;p&gt;For the antidetect browser layer we use &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; because of its local REST API, which lets the orchestrator open, close, and inspect profiles programmatically without UI interaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting Up the BitBrowser Local API
&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.amazonaws.com%2Fuploads%2Farticles%2F2u1ujracyn9rlwsmeqic.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.amazonaws.com%2Fuploads%2Farticles%2F2u1ujracyn9rlwsmeqic.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BitBrowser exposes a local API on &lt;code&gt;http://127.0.0.1:54345&lt;/code&gt; once the desktop client is running. The endpoints we need:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/create&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provision a new profile with custom fingerprint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/open&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Launch a profile and get its CDP WebSocket URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/close&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cleanly close a profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/list&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List all profiles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Modify profile config (proxy, fingerprint)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A minimal Node.js client wrapper:&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;axios&lt;/span&gt; &lt;span class="k"&gt;from&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="nx"&gt;BB&lt;/span&gt; &lt;span class="o"&gt;=&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;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;baseURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:54345&lt;/span&gt;&lt;span class="dl"&gt;'&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;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&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;createProfile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Win11&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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;BB&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/browser/create&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;platform&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://www.reddit.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxyMethod&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxyType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxyUserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxyPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;browserFingerPrint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;ostype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;os&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;120&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US,en&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;proxyConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&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;span class="k"&gt;return&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;data&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="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&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;openProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&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="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="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;BB&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/browser/open&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&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;ws&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;http&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&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;closeProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;BB&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/browser/close&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&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 &lt;code&gt;ws&lt;/code&gt; returned from &lt;code&gt;open&lt;/code&gt; is a Chrome DevTools Protocol WebSocket, which Playwright or Puppeteer can attach to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Proxy Rotation Strategy
&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.amazonaws.com%2Fuploads%2Farticles%2Fejo2lalmmz76hx2yhmau.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.amazonaws.com%2Fuploads%2Farticles%2Fejo2lalmmz76hx2yhmau.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For Reddit, the rotation rules are tighter than for Facebook ads. Three principles drive the proxy layer:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. One proxy per account, sticky for the account's lifetime.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rotating proxies on a single Reddit account is a red flag. The IP must stay consistent within a city-level geography for the account's life. Use sticky residential sessions (most providers offer 10-30 minute or session-bound stickiness; for Reddit, configure for session lifetime).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Proxy geography must match the account's claimed location.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If an account historically posted from US East and suddenly logs in from Frankfurt, Reddit flags it. The orchestrator stores each account's &lt;code&gt;home_geo&lt;/code&gt; and rejects any proxy assignment outside that geography.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. ASN diversity across the fleet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If 8 of your 24 accounts all route through the same ASN (e.g., the same residential provider's Comcast pool), Reddit's correlation systems can cluster them. Mix at least 3 providers across the fleet.&lt;/p&gt;

&lt;p&gt;A simple proxy pool manager:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ProxyPool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignments&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;Map&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="nf"&gt;assignProxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;geoHint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignments&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="nx"&gt;accountId&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;provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;selectProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&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;session&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;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createStickySession&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;country&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;geoHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;country&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;geoHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;sessionDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;unlimited&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;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;socks5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;geoHint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;assignments&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;accountId&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="k"&gt;return&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="nf"&gt;selectProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hashAccountId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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 deterministic provider selection (via account ID hash) keeps the same account on the same provider across orchestrator restarts. This avoids accidental ASN drift.&lt;/p&gt;




&lt;h2&gt;
  
  
  Account State Machine
&lt;/h2&gt;

&lt;p&gt;Not every Reddit account in the fleet should perform every action. A 4-day-old account shouldn't be posting in r/personalfinance. The orchestrator tracks each account through a state machine:&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;ACCOUNT_STATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;WARMING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warming&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// 0-14 days, read-only + light voting&lt;/span&gt;
  &lt;span class="na"&gt;ESTABLISHING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;establishing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 14-60 days, commenting in low-stakes subs&lt;/span&gt;
  &lt;span class="na"&gt;ACTIVE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;// 60+ days, full posting capability&lt;/span&gt;
  &lt;span class="na"&gt;COOLDOWN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cooldown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// post-action rest period&lt;/span&gt;
  &lt;span class="na"&gt;SHADOWBAN_CHECK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;shadowban_check&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;RETIRED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;retired&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;function&lt;/span&gt; &lt;span class="nf"&gt;getAvailableActions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;warming&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browse&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;upvote_low_risk&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;save_post&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;establishing&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browse&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;upvote&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;comment_replies&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;comment_low_stakes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browse&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;upvote&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;comment&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;post&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;crosspost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="nl"&gt;default&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;browse&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The warming phase is the most-skipped step in agency setups, and the most common reason new accounts get filtered. A 14-day pure-browsing warm-up reduces shadowban rate from roughly 35% to under 5% in our internal tracking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mobile Sessions for Account Verification
&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.amazonaws.com%2Fuploads%2Farticles%2Fv4mt2nka32sa51eh10fz.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fv4mt2nka32sa51eh10fz.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Reddit increasingly asks new accounts to verify via email or phone, and some subreddits require mobile-only posting (especially location-tagged communities). Running mobile sessions from a desktop fleet usually means either physical phones or cloud-based Android instances. We use &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; for the cloud Android layer when a brand account needs mobile-app posting alongside its desktop browser presence — it shares fingerprint context with the desktop profile, which keeps the account looking consistent across surfaces.&lt;/p&gt;

&lt;p&gt;The mobile cloud instance gets the same proxy and timezone as the linked desktop profile. Any deviation across surfaces is a fingerprinting tell.&lt;/p&gt;




&lt;h2&gt;
  
  
  Shadowban Detection Loop
&lt;/h2&gt;

&lt;p&gt;Because shadowbans are silent, the orchestrator runs a periodic external check. For each account, every 6 hours:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Submit a test comment from the account.&lt;/li&gt;
&lt;li&gt;From an unrelated proxy (different ASN, different account), fetch the target thread via Reddit's public JSON endpoint.&lt;/li&gt;
&lt;li&gt;Compare: does the comment appear in the public view?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If three consecutive checks fail, the account moves to &lt;code&gt;SHADOWBAN_CHECK&lt;/code&gt; state and the orchestrator stops dispatching actions to it until manually reviewed.&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isShadowbanned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testCommentId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;threadUrl&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;publicView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetchAsAnonymous&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;threadUrl&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.json&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;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findCommentInTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;publicView&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testCommentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;found&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;This is the single most useful piece of monitoring in the entire stack. Without it, you can run a dead account for weeks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Action Pacing and Burstiness
&lt;/h2&gt;

&lt;p&gt;Human Reddit usage is bursty. People scroll for 15 minutes, vote on 8 things, comment once, then disappear for 4 hours. Naïve schedulers space actions evenly, which is a strong bot signal.&lt;/p&gt;

&lt;p&gt;The action dispatcher uses a Poisson-process model with two parameters per account: average actions per active session, and average session duration. Sessions themselves are scheduled on the account's historical posting hours (US East accounts mostly active 9am-11pm ET, etc.). Within a session, action timing is jittered.&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;function&lt;/span&gt; &lt;span class="nf"&gt;scheduleNextAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastActionAt&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;lambda&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actionsPerHour&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;delaySeconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lambda&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastActionAt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;delaySeconds&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&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;h2&gt;
  
  
  What Goes Wrong in Practice
&lt;/h2&gt;

&lt;p&gt;Three failure modes account for most issues:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cookie corruption across profile reuse.&lt;/strong&gt; If a profile is closed mid-action, BitBrowser sometimes flushes cookies inconsistently. Always wait for the &lt;code&gt;/browser/close&lt;/code&gt; response before re-opening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CDP WebSocket disconnect on long-running scripts.&lt;/strong&gt; For Playwright scripts that run more than 30 minutes per session, implement reconnection logic. The CDP socket times out on idle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proxy DNS leakage.&lt;/strong&gt; Even with proxy configured at the BitBrowser level, ensure WebRTC is disabled and DNS resolution is forced through the proxy. BitBrowser's fingerprint settings include both toggles.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Closing Notes
&lt;/h2&gt;

&lt;p&gt;A multi-account Reddit backend is more about discipline than tooling. The browser layer, proxy layer, and orchestration layer all have to enforce the same constraint: each account behaves like a separate human, consistently, over months. Cut corners on any single layer and the entire fleet pays for it.&lt;/p&gt;

&lt;p&gt;The full reference implementation we use in production is around 4,000 lines of TypeScript. The architecture above is the load-bearing 20%. If you build the proxy stickiness, the state machine, and the shadowban detection loop correctly, the rest is glue.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Building Multi-Account Browser Automation with Playwright + Residential Proxies</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Mon, 18 May 2026 22:15:35 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/building-multi-account-browser-automation-with-playwright-residential-proxies-44bj</link>
      <guid>https://dev.to/digitalgrowthpro/building-multi-account-browser-automation-with-playwright-residential-proxies-44bj</guid>
      <description>&lt;p&gt;If you have ever tried to run automated browser sessions across multiple accounts — for QA testing, price monitoring, ad verification, or managing several business profiles — you have probably hit the same wall everyone hits. Everything works fine for the first account, then the second account starts seeing CAPTCHAs, the third gets a verification prompt, and by the fourth the platform has quietly linked all of them together.&lt;/p&gt;

&lt;p&gt;The problem is almost never your code. It is the fact that every session is leaving the same fingerprint and the same IP address. This guide walks through how to build a multi-account browser automation system with &lt;strong&gt;Playwright&lt;/strong&gt; and &lt;strong&gt;residential proxies&lt;/strong&gt; that keeps each session genuinely isolated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Multi-Account Automation Fails
&lt;/h2&gt;

&lt;p&gt;Modern platforms identify users on two independent layers, and you have to defeat both.&lt;/p&gt;

&lt;p&gt;The first layer is &lt;strong&gt;network identity&lt;/strong&gt;. If ten "different" users all connect from the same datacenter IP, no amount of clever scripting will save you. The IP itself is the giveaway.&lt;/p&gt;

&lt;p&gt;The second layer is &lt;strong&gt;browser fingerprinting&lt;/strong&gt;. Even on separate IPs, websites read dozens of signals from the browser — canvas hashes, WebGL renderer strings, installed fonts, screen resolution, timezone, hardware concurrency, the AudioContext signature, and more. Combined, these create a fingerprint that is unique enough to track you across sessions. Two Playwright contexts launched from the same machine with default settings will produce nearly identical fingerprints.&lt;/p&gt;

&lt;p&gt;A robust setup therefore needs three things working together: isolated browser contexts, a unique and consistent fingerprint per account, and a dedicated residential IP per account.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Isolate Sessions with Browser Contexts
&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.amazonaws.com%2Fuploads%2Farticles%2Fml1aqtje01fntotcgyb3.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fml1aqtje01fntotcgyb3.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Playwright's &lt;code&gt;browserContext&lt;/code&gt; is the foundation. Each context has its own cookie jar, local storage, cache, and session storage. Nothing leaks between contexts, which means account A's login session is invisible to account B.&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&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;playwright&lt;/span&gt;&lt;span class="dl"&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;createAccountSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accountConfig&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;browser&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;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;context&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timezoneId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;accountConfig&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;page&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;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&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="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&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;A common mistake is to open multiple &lt;em&gt;pages&lt;/em&gt; inside one context and treat them as separate accounts. They are not separate — they share cookies and storage. Always use one context per account, and ideally one browser process per account so that crashes stay isolated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Add Residential Proxies
&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.amazonaws.com%2Fuploads%2Farticles%2Fpx9vb1082j5kblq3ovmt.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fpx9vb1082j5kblq3ovmt.jpg" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;br&gt;
This is the single most important part. Datacenter proxies are cheap, but platforms maintain lists of known datacenter IP ranges and flag them instantly. &lt;strong&gt;Residential proxies&lt;/strong&gt; route traffic through real ISP-assigned home connections, so each session looks like an ordinary household visitor.&lt;/p&gt;

&lt;p&gt;Playwright accepts proxy configuration directly at the context level:&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;accountConfig&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&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://proxy-host:port&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-proxy-user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-proxy-pass&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="c1"&gt;// ... fingerprint fields&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules matter when assigning proxies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One IP per account, kept consistent.&lt;/strong&gt; If account A logs in from a New York IP one day and a Berlin IP the next, that is a red flag. Use sticky sessions so each account keeps the same IP across runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Match the IP geography to the account.&lt;/strong&gt; A profile that claims to be in Spain but connects from a Vietnamese IP is an obvious mismatch. The proxy location, the browser locale, and the timezone all need to agree.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not over-rotate.&lt;/strong&gt; Rotating IPs mid-session is for scraping public pages, not for account management. Logged-in sessions should look stable.&lt;/p&gt;

&lt;p&gt;For testing your setup, providers like NodeMaven, IPRoyal, or SOAX offer residential pools with sticky-session support. Whichever you choose, verify it lets you hold an IP for the length of a working session.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Build a Consistent Fingerprint Per Account
&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.amazonaws.com%2Fuploads%2Farticles%2F9xsji9mdn29ua6as7sjb.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F9xsji9mdn29ua6as7sjb.jpg" alt=" " width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is where most DIY setups quietly fail. Playwright lets you set the user agent, viewport, locale, and timezone — but websites read far more than that. Canvas fingerprinting, WebGL vendor and renderer strings, AudioContext, the navigator object, font enumeration, and WebRTC all need to be consistent and believable.&lt;/p&gt;

&lt;p&gt;You can patch some of these manually with &lt;code&gt;addInitScript&lt;/code&gt;:&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInitScript&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hardwareConcurrency&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;get&lt;/span&gt;&lt;span class="p"&gt;:&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;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deviceMemory&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;get&lt;/span&gt;&lt;span class="p"&gt;:&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;8&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;But doing this thoroughly is a serious project. You have to spoof canvas without producing an &lt;em&gt;impossible&lt;/em&gt; canvas, keep the WebGL strings matched to a real GPU, block WebRTC from leaking the true IP, and — critically — make sure the &lt;strong&gt;same&lt;/strong&gt; fingerprint reappears every single time that account runs. An account whose fingerprint changes on every login looks more suspicious than one that never hid at all.&lt;/p&gt;

&lt;p&gt;This is the point where many developers stop hand-rolling and move to a purpose-built tool.&lt;/p&gt;




&lt;h2&gt;
  
  
  A More Reliable Approach: Antidetect Browsers with Automation Support
&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.amazonaws.com%2Fuploads%2Farticles%2Fec61bw66nla63hlmrc43.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.amazonaws.com%2Fuploads%2Farticles%2Fec61bw66nla63hlmrc43.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Rather than patching dozens of APIs by hand, an &lt;strong&gt;antidetect browser&lt;/strong&gt; generates a complete, internally consistent fingerprint for each profile and stores it so the profile reopens identically every time. The profiles are real Chromium environments, so Playwright and Puppeteer can still drive them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; is one option that exposes a local automation API. You create a profile in the app, assign its proxy, and then connect Playwright over the Chrome DevTools Protocol endpoint the API returns:&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;chromium&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;playwright&lt;/span&gt;&lt;span class="dl"&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;launchProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Ask the local API to open the profile&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:54345/browser/open&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&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;wsEndpoint&lt;/span&gt; &lt;span class="o"&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Connect Playwright to the already-running browser&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&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;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connectOverCDP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsEndpoint&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&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="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;page&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 advantage is that fingerprint consistency, proxy binding, and profile storage are handled for you, while you keep full Playwright scripting on top. You write automation logic; the browser handles the identity layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Handle Mobile-First Platforms
&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.amazonaws.com%2Fuploads%2Farticles%2Fqemrlbv6vbsdffwznwzo.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.amazonaws.com%2Fuploads%2Farticles%2Fqemrlbv6vbsdffwznwzo.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some platforms — short-video apps, certain marketplaces, messaging services — barely tolerate desktop browser access and expect a real mobile device. A desktop fingerprint, however perfect, cannot fully imitate a phone's sensor data and app environment.&lt;/p&gt;

&lt;p&gt;For those cases a &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;cloud phone&lt;/a&gt; runs a genuine Android instance in the cloud, each with its own hardware identifiers. You manage it remotely, but to the platform it is an ordinary mobile device. It is the natural complement to a desktop antidetect setup when your accounts live on mobile-first platforms.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Make the Automation Behave Like a Human
&lt;/h2&gt;

&lt;p&gt;Even with perfect isolation, robotic &lt;em&gt;behavior&lt;/em&gt; gives you away. Build these habits into every script:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Randomize timing.&lt;/strong&gt; Never click instantly. Insert variable delays between actions.&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;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;800&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Type, do not paste.&lt;/strong&gt; Use &lt;code&gt;page.type()&lt;/code&gt; with a per-character delay rather than filling the value in one shot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stagger your accounts.&lt;/strong&gt; Running 30 accounts in a synchronized burst at 09:00 produces an obvious machine pattern. Spread activity across the day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Limit concurrency.&lt;/strong&gt; Each isolated browser eats real RAM. Run accounts in small batches and queue the rest rather than launching everything at once.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting It Together
&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.amazonaws.com%2Fuploads%2Farticles%2Fkqbfxwuhotwszrmkps19.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fkqbfxwuhotwszrmkps19.jpg" alt=" " width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A production multi-account automation system has a clear shape:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;profile store&lt;/strong&gt; holding each account's fingerprint, proxy, and credentials.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;launcher&lt;/strong&gt; that opens an isolated browser per account and connects Playwright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task scripts&lt;/strong&gt; containing the per-account automation logic.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;scheduler&lt;/strong&gt; that staggers runs and caps concurrency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health checks&lt;/strong&gt; that detect CAPTCHAs, logouts, and verification prompts early.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The architectural principle is separation of concerns. Your Playwright scripts should never care about &lt;em&gt;how&lt;/em&gt; identity isolation works — they just receive a clean, connected &lt;code&gt;page&lt;/code&gt;. Whether that isolation comes from hand-patched contexts or from an antidetect browser with an API, the automation layer stays the same. That separation is what lets the system scale from five accounts to fifty without a rewrite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Notes
&lt;/h2&gt;

&lt;p&gt;Multi-account browser automation is a solvable engineering problem, but only when you respect both detection layers. Isolated contexts alone are not enough. Residential proxies alone are not enough. Fingerprint spoofing alone is not enough. The three have to work together, consistently, for every account, on every run.&lt;/p&gt;

&lt;p&gt;Start small. Get two accounts running cleanly with full isolation before you scale. Verify each profile against a fingerprinting test page, confirm the IP geography matches the locale and timezone, and only then add more accounts to the rotation.&lt;/p&gt;

&lt;p&gt;Finally, stay inside the terms of service of whatever platforms you work with. The techniques here are about &lt;em&gt;legitimate&lt;/em&gt; session isolation — QA across environments, ad verification, price monitoring, and managing business accounts you are entitled to operate. Used that way, a Playwright-plus-residential-proxy stack is a reliable, maintainable foundation.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>webdev</category>
      <category>automation</category>
      <category>antidetect</category>
    </item>
    <item>
      <title>Integrating BitBrowser with n8n: Automating Profile Creation via REST API</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Fri, 15 May 2026 16:38:51 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/integrating-bitbrowser-with-n8n-automating-profile-creation-via-rest-api-21mn</link>
      <guid>https://dev.to/digitalgrowthpro/integrating-bitbrowser-with-n8n-automating-profile-creation-via-rest-api-21mn</guid>
      <description>&lt;p&gt;If you manage dozens — or hundreds — of browser profiles for marketing operations, e-commerce, or QA testing, you already know the bottleneck: profile creation and configuration eat hours every week. Click here, set proxy there, copy-paste user agent, save, repeat. It's the kind of repetitive work that begs to be automated.&lt;/p&gt;

&lt;p&gt;This guide walks through building a fully automated profile-creation pipeline using &lt;strong&gt;n8n&lt;/strong&gt; (the open-source workflow automation tool) and the &lt;strong&gt;BitBrowser local REST API&lt;/strong&gt;. By the end, you'll have a workflow that ingests a list of accounts from a Google Sheet, generates a unique fingerprint per profile, attaches a proxy from your provider, and creates the BitBrowser profile — all without a single click.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why n8n + BitBrowser Is a Strong Pairing
&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.amazonaws.com%2Fuploads%2Farticles%2Fp0vkbhgru0rnawymw2dt.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.amazonaws.com%2Fuploads%2Farticles%2Fp0vkbhgru0rnawymw2dt.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;n8n is self-hosted, node-based, and supports HTTP requests, scheduling, conditional logic, and 400+ native integrations. It runs locally or on a small VPS, which keeps your account data and credentials off third-party SaaS platforms — a real advantage for anyone handling sensitive marketing operations.&lt;/p&gt;

&lt;p&gt;BitBrowser, on the other hand, exposes a complete local REST API on port &lt;code&gt;54345&lt;/code&gt; by default. Every action you can perform manually in the GUI — creating a profile, editing fingerprints, launching a browser instance, deleting a profile — is available as a JSON endpoint. That makes it one of the most automation-friendly antidetect browsers on the market.&lt;/p&gt;

&lt;p&gt;When you connect the two, n8n becomes your orchestration brain and BitBrowser becomes your execution engine. You can register for &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser here&lt;/a&gt; if you don't already have an account — the free tier is enough to test this workflow with up to 10 profiles.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before building the workflow, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BitBrowser desktop client&lt;/strong&gt; installed and running locally (the API only responds when the client is open)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n8n&lt;/strong&gt; installed — either via Docker, npm (&lt;code&gt;npm install n8n -g&lt;/code&gt;), or a hosted instance&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Google Sheet&lt;/strong&gt; with columns for account name, proxy IP, proxy port, proxy username, and proxy password&lt;/li&gt;
&lt;li&gt;A residential or ISP proxy provider (any provider that gives you authenticated &lt;code&gt;host:port:user:pass&lt;/code&gt; credentials works)&lt;/li&gt;
&lt;li&gt;Basic familiarity with HTTP requests and JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll also need to verify the BitBrowser API is reachable. Open a terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://127.0.0.1:54345/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you get a &lt;code&gt;200 OK&lt;/code&gt; response, you're ready to proceed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Understanding the BitBrowser API Endpoints
&lt;/h2&gt;

&lt;p&gt;BitBrowser's API is documented locally, but the three endpoints we'll use in this workflow are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/update&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Create or update a browser profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/open&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Launch a profile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/browser/close&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;td&gt;Close a running profile&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;/browser/update&lt;/code&gt; endpoint is the workhorse. It accepts a JSON body containing the profile name, group ID, proxy configuration, fingerprint settings, and platform metadata. If you don't pass an &lt;code&gt;id&lt;/code&gt;, it creates a new profile; if you pass an existing &lt;code&gt;id&lt;/code&gt;, it updates that profile.&lt;/p&gt;

&lt;p&gt;A minimal request body looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Profile_001"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"groupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"socks5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"proxy.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyUserName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyPassword"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pass456"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browserFingerPrint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"coreVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"124"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ostype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Win32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"124"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"userAgent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en-US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"America/New_York"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leaving &lt;code&gt;userAgent&lt;/code&gt; empty tells BitBrowser to auto-generate a consistent one based on the OS and browser version — which is almost always what you want for realistic fingerprints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Designing the n8n Workflow
&lt;/h2&gt;

&lt;p&gt;The workflow has six nodes, each with a clear responsibility:&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.amazonaws.com%2Fuploads%2Farticles%2F8mea92zt238817mpyd3i.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.amazonaws.com%2Fuploads%2Farticles%2F8mea92zt238817mpyd3i.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Schedule Trigger&lt;/strong&gt; — runs the workflow on a cron schedule (e.g., daily at 9 AM)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Sheets&lt;/strong&gt; — reads the queue of accounts to provision&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Node&lt;/strong&gt; — formats the payload, generates randomized fingerprint values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Request&lt;/strong&gt; — calls the BitBrowser &lt;code&gt;/browser/update&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IF Node&lt;/strong&gt; — checks the response success flag&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Sheets (update)&lt;/strong&gt; — writes the new profile ID back to the sheet, marking the row as "provisioned"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern gives you idempotency: rows already marked as provisioned are skipped on the next run, so you can safely re-trigger the workflow without creating duplicates.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Building the HTTP Request Node
&lt;/h2&gt;

&lt;p&gt;This is the only node that needs careful attention. In n8n's HTTP Request node, configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Method&lt;/strong&gt;: POST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;http://127.0.0.1:54345/browser/update&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: None (the local API is unauthenticated by default — keep your firewall closed to external traffic)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body Content Type&lt;/strong&gt;: JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specify Body&lt;/strong&gt;: Using JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the JSON body, reference the upstream Set node's output using n8n expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.accountName }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"groupId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://www.google.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyMethod"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.proxyType }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.proxyHost }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.proxyPort }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyUserName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.proxyUser }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proxyPassword"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.proxyPass }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"browserFingerPrint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"coreVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"124"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ostype"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Win32"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"124"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"language"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.language }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeZone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.timeZone }}"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the workflow runs, n8n substitutes each &lt;code&gt;{{ }}&lt;/code&gt; expression with the value from the corresponding row in your Google Sheet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Handling Mobile Profiles
&lt;/h2&gt;

&lt;p&gt;If your operation involves managing mobile-only platforms like TikTok Shop seller dashboards or mobile-app onboarding, desktop profiles aren't enough. This is where the &lt;strong&gt;BitBrowser Cloud Phone&lt;/strong&gt; service becomes useful — it provisions real Android cloud devices that you can control programmatically via the same API ecosystem.&lt;/p&gt;

&lt;p&gt;You can integrate cloud phone provisioning into the same n8n workflow by adding a second branch that calls the &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone service endpoints&lt;/a&gt;. The pattern is identical: read the row from the sheet, hit the appropriate endpoint, write back the device ID. This lets you mix desktop and mobile profile creation in a single automated pipeline — a powerful setup for teams that need consistent device-level isolation across both form factors.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Error Handling and Retries
&lt;/h2&gt;

&lt;p&gt;Production workflows always fail eventually. Proxies go offline, the BitBrowser client crashes, network blips happen. Build in resilience with these patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Retry on failure&lt;/strong&gt;: In the HTTP Request node settings, enable "Retry on Fail" with 3 attempts and a 2-second backoff. Most transient errors resolve on retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error workflow&lt;/strong&gt;: Set a global error workflow in n8n that posts to a Slack or Telegram channel whenever the main workflow fails. This way you find out about issues before your account list goes stale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Health-check node&lt;/strong&gt;: Add an initial HTTP Request node that hits &lt;code&gt;/health&lt;/code&gt; on the BitBrowser API. If it returns anything other than &lt;code&gt;200&lt;/code&gt;, route the workflow to a notification branch and exit early. This prevents the workflow from silently failing when the BitBrowser client isn't running.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Scaling Considerations
&lt;/h2&gt;

&lt;p&gt;Once the basic workflow runs reliably, a few optimizations matter:&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.amazonaws.com%2Fuploads%2Farticles%2Fcv54i9d0icers7fe5mvs.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.amazonaws.com%2Fuploads%2Farticles%2Fcv54i9d0icers7fe5mvs.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batch size&lt;/strong&gt;: Don't create 500 profiles in one workflow run. BitBrowser handles concurrent API calls fine, but file I/O on the underlying SQLite database can slow down. Process 20–50 profiles per run and schedule multiple runs throughout the day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt;: Insert a Wait node (1–2 seconds) between profile creations if you're hitting any rate limits on your proxy provider's API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging&lt;/strong&gt;: Pipe the n8n execution logs to a persistent store (Postgres, Elasticsearch) so you can audit which profile was created when, with which proxy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt;: Export your n8n workflow as JSON and commit it to a Git repository. Workflows evolve; treat them like code.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Security Notes
&lt;/h2&gt;

&lt;p&gt;The BitBrowser local API is unauthenticated by design — it assumes you're the only one with access to &lt;code&gt;localhost&lt;/code&gt;. Two things to keep in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Never expose port &lt;code&gt;54345&lt;/code&gt; to the public internet. If you need remote access, tunnel through SSH or use a service like Tailscale.&lt;/li&gt;
&lt;li&gt;If you run n8n on a separate machine, bind it to a private network or VPN. Don't put it behind a public reverse proxy without authentication enabled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most operators, running both n8n and BitBrowser on the same workstation (or the same internal VM) is the simplest and most secure setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;The combination of n8n's orchestration capabilities and BitBrowser's open local API makes profile automation genuinely accessible — no custom code required, just JSON and HTTP nodes. Once you've built this workflow once, you'll wonder how you managed without it.&lt;/p&gt;

&lt;p&gt;The same pattern extends to launching browsers on schedule, rotating proxies across existing profiles, archiving inactive accounts, and syncing profile metadata to a central dashboard. Treat the workflow you built today as the first brick in a much larger automation system.&lt;/p&gt;

&lt;p&gt;If you're new to BitBrowser and want to test this workflow, you can &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;create a free account here&lt;/a&gt; and have a working profile pipeline running in under an hour.&lt;/p&gt;

&lt;p&gt;Happy automating.&lt;/p&gt;

</description>
      <category>automation</category>
      <category>ai</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>BitBrowser + Puppeteer: Automating Multi-Profile Browser Sessions</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Mon, 27 Apr 2026 20:08:04 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/bitbrowser-puppeteer-automating-multi-profile-browser-sessions-486g</link>
      <guid>https://dev.to/digitalgrowthpro/bitbrowser-puppeteer-automating-multi-profile-browser-sessions-486g</guid>
      <description>&lt;p&gt;Puppeteer is one of the most widely used tools for browser automation — but it has a well-known limitation when it comes to multi-profile operations: every Chromium instance it launches shares the same underlying browser fingerprint. For tasks that require isolated, independent browser identities across multiple sessions, standard Puppeteer falls short.&lt;/p&gt;

&lt;p&gt;BitBrowser solves this by providing persistent, isolated browser profiles with unique fingerprints per session. Each profile has its own Canvas signature, WebGL renderer, screen parameters, fonts, timezone, and User Agent — and exposes a remote debugging port that Puppeteer can connect to directly. The result is a Puppeteer automation stack where each session carries a genuinely distinct browser identity.&lt;/p&gt;

&lt;p&gt;This guide covers the full integration: connecting Puppeteer to BitBrowser profiles via the remote debugging API, managing multiple profile sessions, and structuring a multi-profile automation workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  How BitBrowser Exposes Browser Sessions to Puppeteer
&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.amazonaws.com%2Fuploads%2Farticles%2Fas08qst7giepa3lilenx.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fas08qst7giepa3lilenx.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BitBrowser is built on Chromium and supports Chrome DevTools Protocol (CDP) — the same protocol Puppeteer uses to control browser instances. When you launch a profile through BitBrowser's local API, it starts a Chromium instance with that profile's fingerprint parameters loaded, and exposes a WebSocket debugging endpoint that Puppeteer can attach to using &lt;code&gt;puppeteer.connect()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is different from &lt;code&gt;puppeteer.launch()&lt;/code&gt;, which starts a fresh Chromium instance with no persistent fingerprint. With &lt;code&gt;puppeteer.connect()&lt;/code&gt;, Puppeteer attaches to an already-running browser session — in this case one that BitBrowser has fully initialized with the correct fingerprint, proxy, and session data for that profile.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Node.js 18 or higher&lt;/li&gt;
&lt;li&gt;Puppeteer: &lt;code&gt;npm install puppeteer-core&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;BitBrowser installed and running locally&lt;/li&gt;
&lt;li&gt;At least one configured profile in BitBrowser with a proxy assigned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note: use &lt;code&gt;puppeteer-core&lt;/code&gt; rather than the full &lt;code&gt;puppeteer&lt;/code&gt; package since BitBrowser provides its own Chromium binary. You do not need Puppeteer to download a separate browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Start a Profile via BitBrowser's Local API
&lt;/h2&gt;

&lt;p&gt;BitBrowser exposes a local REST API on &lt;code&gt;http://127.0.0.1:54345&lt;/code&gt; that allows you to open, close, and query the status of browser profiles programmatically.&lt;/p&gt;

&lt;p&gt;To open a profile and retrieve its debugging endpoint:&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="nx"&gt;BITBROWSER_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:54345&lt;/span&gt;&lt;span class="dl"&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;openProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&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;response&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/browser/open`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&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;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Returns the WebSocket debugger URL for this profile&lt;/span&gt;
    &lt;span class="k"&gt;return&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Failed to open profile: &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;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&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 &lt;code&gt;ws&lt;/code&gt; field in the response contains the WebSocket URL that Puppeteer will connect to — something like &lt;code&gt;ws://127.0.0.1:9222/devtools/browser/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Connect Puppeteer to the Running Profile
&lt;/h2&gt;

&lt;p&gt;Once the profile is open and you have the WebSocket endpoint, connect Puppeteer to it:&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;puppeteer&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;puppeteer-core&lt;/span&gt;&lt;span class="dl"&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;connectToProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wsEndpoint&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;browser&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browserWSEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;browser&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;Setting &lt;code&gt;defaultViewport: null&lt;/code&gt; tells Puppeteer not to override the viewport — important here because the viewport is part of the fingerprint that BitBrowser has already configured for the profile. Overriding it would create a mismatch between the reported screen parameters and the actual viewport.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Full Single-Profile Automation Flow
&lt;/h2&gt;

&lt;p&gt;Putting the two steps together into a complete single-profile session:&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;puppeteer&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;puppeteer-core&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;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="nx"&gt;BITBROWSER_API&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://127.0.0.1:54345&lt;/span&gt;&lt;span class="dl"&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;runProfileSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskFn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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="c1"&gt;// Open the profile and get the debugger endpoint&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;openRes&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/browser/open`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;openRes&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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Could not open profile &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;openRes&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;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;wsEndpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;openRes&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Connect Puppeteer to the running profile&lt;/span&gt;
    &lt;span class="nx"&gt;browser&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;browserWSEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wsEndpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Get the existing page or open a new one&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pages&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;pages&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="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the task passed in as a function&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;taskFn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Session error for profile &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&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="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Disconnect Puppeteer without closing the browser&lt;/span&gt;
    &lt;span class="c1"&gt;// This preserves the session state in BitBrowser&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Close the profile via BitBrowser API&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/browser/close`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&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 &lt;code&gt;browser.disconnect()&lt;/code&gt; call is important: it detaches Puppeteer from the session without closing the Chromium instance. This allows BitBrowser to cleanly save the session state — cookies, localStorage, login state — before the profile is closed via the API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Multi-Profile Sequential Automation
&lt;/h2&gt;

&lt;p&gt;For multi-profile workflows, the safest approach is running profiles sequentially rather than in parallel. This avoids resource contention on lower-spec machines and prevents any timing-based behavioral clustering across sessions:&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;profiles&lt;/span&gt; &lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile_id_1&lt;/span&gt;&lt;span class="dl"&gt;'&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;Account A&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile_id_2&lt;/span&gt;&lt;span class="dl"&gt;'&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;Account B&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;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profile_id_3&lt;/span&gt;&lt;span class="dl"&gt;'&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;Account C&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runAllProfiles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &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;profile&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Running session for: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runProfileSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&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="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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://example.com/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#main-content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Your per-profile task logic here&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - Page title: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Add a delay between profile sessions&lt;/span&gt;
    &lt;span class="c1"&gt;// Avoids identical session timing patterns across accounts&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2000&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;span class="nf"&gt;runAllProfiles&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The randomized delay between sessions (&lt;code&gt;3000 + Math.random() * 2000&lt;/code&gt; milliseconds) is deliberate. Identical session start times across multiple accounts is a behavioral pattern that analytics systems can detect. Adding variance makes the timing look more organic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Parallel Sessions for Higher Throughput
&lt;/h2&gt;

&lt;p&gt;If sequential execution is too slow for your use case, you can run profiles in parallel — but limit concurrency to avoid overwhelming system resources:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runInBatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taskFn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;batchSize&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;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;runProfileSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&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="nx"&gt;taskFn&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;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Completed batch &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;batchSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Pause between batches&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run 3 profiles at a time&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runInBatches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&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://example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// task logic&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Batch size depends on your machine's available memory. Each BitBrowser profile runs a full Chromium instance, which typically consumes 200–400MB RAM. For a machine with 16GB RAM, a batch size of 4–6 is a reasonable starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retrieving Profile IDs Programmatically
&lt;/h2&gt;

&lt;p&gt;Rather than hardcoding profile IDs, you can query the BitBrowser API to list all available profiles:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;listProfiles&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;response&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/browser/list`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;page&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="na"&gt;pageSize&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="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="p"&gt;}));&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;listProfiles&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Found &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; profiles`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes your automation scripts portable — they discover available profiles at runtime rather than relying on hardcoded IDs that break when profiles are renamed or reorganized.&lt;/p&gt;




&lt;h2&gt;
  
  
  Handling Profile Open Failures Gracefully
&lt;/h2&gt;

&lt;p&gt;In production automation, profiles occasionally fail to open — the BitBrowser API might time out, a proxy might be unreachable, or the Chromium instance might take longer than expected to initialize. A simple retry wrapper prevents single-profile failures from breaking your entire batch:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;openProfileWithRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&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;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;BITBROWSER_API&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/browser/open`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;profileId&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ws&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;msg&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;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Profile &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; open attempt &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;error&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="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
      &lt;span class="p"&gt;}&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;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Profile &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;profileId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; failed after &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;maxRetries&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; attempts`&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;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 exponential backoff (&lt;code&gt;2000 * attempt&lt;/code&gt;) gives the system time to recover between retries without flooding the API with rapid repeated requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Integration Notes
&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.amazonaws.com%2Fuploads%2Farticles%2Fx9l461pmc1ef8mtqmszo.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fx9l461pmc1ef8mtqmszo.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always use &lt;code&gt;browser.disconnect()&lt;/code&gt;, not &lt;code&gt;browser.close()&lt;/code&gt;&lt;/strong&gt; — closing the browser through Puppeteer bypasses BitBrowser's session-saving logic. Disconnecting and then closing through the API preserves cookie and localStorage state correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not override viewport&lt;/strong&gt; — &lt;code&gt;defaultViewport: null&lt;/code&gt; is mandatory. The viewport dimensions are part of the fingerprint configuration BitBrowser sets per profile. Overriding them creates a detectable inconsistency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Proxy is handled at the profile level&lt;/strong&gt; — you do not need to configure proxy settings in Puppeteer. BitBrowser loads the proxy assigned to each profile automatically when it opens. Your Puppeteer code connects to an already-proxied session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session persistence is per profile&lt;/strong&gt; — cookies, localStorage, and login state are saved per profile by BitBrowser between sessions. Your automation scripts can rely on previously authenticated sessions without needing to re-login on every run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Practical Applications
&lt;/h2&gt;

&lt;p&gt;This integration is used for a range of legitimate automation tasks: automated account health checks across multiple managed accounts, scheduled content operations across isolated profiles, data collection workflows that require distinct browser identities per session, and QA testing scenarios that need to verify behavior across multiple independent user contexts.&lt;/p&gt;

&lt;p&gt;For workflows that extend to mobile environments — where some platforms behave differently or provide different data through mobile sessions — &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; provides isolated Android environments that can be integrated into the same operational stack alongside desktop BitBrowser profiles.&lt;/p&gt;

&lt;p&gt;If you have not set up BitBrowser yet, &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;register here&lt;/a&gt; to get access to the local API and start building profile-based automation workflows. The API documentation covers additional endpoints for profile creation, configuration updates, and status monitoring beyond what this guide covers.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>automation</category>
      <category>marketing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>BitBrowser + Selenium: Setting Up Automated Browser Sessions with Real Fingerprints</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Thu, 16 Apr 2026 21:10:01 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i</link>
      <guid>https://dev.to/digitalgrowthpro/bitbrowser-selenium-setting-up-automated-browser-sessions-with-real-fingerprints-48i</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fsgrj0psau4j0pb17wa8o.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fsgrj0psau4j0pb17wa8o.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Selenium is the go-to automation framework for most developers who need to control browsers programmatically. But standard Selenium sessions have a problem that becomes obvious the moment you point them at any platform with basic bot detection: they announce themselves. The &lt;code&gt;navigator.webdriver&lt;/code&gt; flag is set to &lt;code&gt;true&lt;/code&gt;, the canvas and WebGL fingerprints are generic and inconsistent, and the browser profile is blank on every run. Any detection system worth its salt will flag the session within seconds.&lt;/p&gt;

&lt;p&gt;The typical workarounds — patching ChromeDriver, injecting JavaScript to override &lt;code&gt;navigator.webdriver&lt;/code&gt;, or using stealth libraries — are a constant arms race. Platforms update their detection, the stealth patches need to catch up, and you spend more time maintaining the bypass layer than building the actual automation logic.&lt;/p&gt;

&lt;p&gt;A more durable approach is to connect Selenium not to a fresh ChromeDriver instance, but to an existing browser profile that already carries a realistic, persistent fingerprint. That is exactly what the BitBrowser local API enables. Instead of launching a new browser, Selenium attaches to a running BitBrowser profile via its remote debugging port — inheriting all the fingerprint settings, cookies, and session state that profile carries.&lt;/p&gt;

&lt;p&gt;This guide covers the full technical setup: how the connection works, the Python implementation, and the practical patterns that make this approach stable at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BitBrowser's Local API Works
&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.amazonaws.com%2Fuploads%2Farticles%2Fyi7y8xkex2n408ucortu.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fyi7y8xkex2n408ucortu.jpg" alt=" " width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BitBrowser runs a local HTTP server on port &lt;code&gt;54345&lt;/code&gt; by default. This API exposes endpoints for managing browser profiles programmatically — creating, opening, closing, and querying profiles from external scripts.&lt;/p&gt;

&lt;p&gt;When you open a profile through the API, BitBrowser launches a Chromium instance for that profile and returns a &lt;code&gt;webSocketDebuggerUrl&lt;/code&gt; — the remote debugging address that Chrome DevTools Protocol uses. Selenium's &lt;code&gt;webdriver.Chrome&lt;/code&gt; can attach to an existing Chrome session using this address via &lt;code&gt;ChromeOptions.debugger_address&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your script calls the BitBrowser API to open a specific profile&lt;/li&gt;
&lt;li&gt;BitBrowser launches the profile and returns the debugger port&lt;/li&gt;
&lt;li&gt;Selenium connects to that port using &lt;code&gt;ChromeOptions&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You now control a live session with the profile's full fingerprint context&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key difference from a standard &lt;code&gt;webdriver.Chrome()&lt;/code&gt; call is that you are not creating a browser — you are attaching to one that already exists. The fingerprint was set when the profile was configured in BitBrowser. Selenium just drives the session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python Implementation
&lt;/h2&gt;

&lt;p&gt;Install the required dependencies first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;selenium requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the base connection function:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.chrome.options&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.chrome.service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Service&lt;/span&gt;

&lt;span class="n"&gt;BITBROWSER_API&lt;/span&gt; &lt;span class="o"&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;open_profile&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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 a BitBrowser profile and return the debugger address.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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;BITBROWSER_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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;data&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="c1"&gt;# Returns ws://127.0.0.1:{port}/devtools/browser/{id}
&lt;/span&gt;        &lt;span class="n"&gt;debugger_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;selenium&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;debugger_url&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;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Failed to open profile: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&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="si"&gt;}&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_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debugger_address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Attach Selenium to the running BitBrowser profile.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Strip the ws:// prefix — Chrome options expects host:port format
&lt;/span&gt;    &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;debugger_address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&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="sh"&gt;""&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="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_experimental_option&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debuggerAddress&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Point to BitBrowser's bundled ChromeDriver
&lt;/span&gt;    &lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;executable_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;C:\Program Files\BitBrowser\chrome\chromedriver.exe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Chrome&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&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;driver&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;close_profile&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Close the profile gracefully after automation.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;requests&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;BITBROWSER_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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A full automation session using these helpers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_automation&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;debugger_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open_profile&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Allow profile to fully initialize
&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debugger_url&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;driver&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;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Verify the fingerprint is active — webdriver flag should be absent
&lt;/span&gt;        &lt;span class="n"&gt;wd_flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_script&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;return navigator.webdriver&lt;/span&gt;&lt;span class="sh"&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;navigator.webdriver: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;wd_flag&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="c1"&gt;# Should return None or False
&lt;/span&gt;
        &lt;span class="c1"&gt;# Your automation logic here
&lt;/span&gt;        &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&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;Page title: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&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="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Do NOT call driver.quit() — that would close the profile
&lt;/span&gt;        &lt;span class="c1"&gt;# Just detach and close via the API
&lt;/span&gt;        &lt;span class="nf"&gt;close_profile&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="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;run_automation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-profile-id-here&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;Note the &lt;code&gt;driver.quit()&lt;/code&gt; warning in the code. Calling &lt;code&gt;quit()&lt;/code&gt; on a Selenium session connected to BitBrowser will close the underlying Chromium process, which can corrupt the profile's state. Always close via the BitBrowser API instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Profile IDs Programmatically
&lt;/h2&gt;

&lt;p&gt;Rather than hardcoding profile IDs, you can query the list endpoint to select profiles dynamically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;list_profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fetch available profiles from BitBrowser.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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;BITBROWSER_API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/browser/list&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;page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pageSize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;page_size&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&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="n"&gt;data&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;return&lt;/span&gt; &lt;span class="n"&gt;data&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;list&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="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_profile_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Find a specific profile by its display name.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list_profiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&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;profile&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;profiles&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;profile&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="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;name&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;profile&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

&lt;span class="c1"&gt;# Usage
&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_profile_by_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LinkedIn - Client A&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;profile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_automation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;profile&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for multi-profile workflows where you cycle through accounts sequentially — each profile carries its own persistent identity, and your automation logic stays generic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running Multiple Profiles Concurrently
&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.amazonaws.com%2Fuploads%2Farticles%2F7imbouljkjcmdbop4595.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F7imbouljkjcmdbop4595.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For parallel automation across several profiles, use Python's &lt;code&gt;concurrent.futures&lt;/code&gt;:&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;concurrent.futures&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_completed&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_profile&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Wrapper that returns results per profile.&lt;/span&gt;&lt;span class="sh"&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;debugger_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open_profile&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;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debugger_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;driver&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;https://target-site.com/dashboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_element&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;css selector&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;.metric-value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;

        &lt;span class="nf"&gt;close_profile&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="k"&gt;return&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="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&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;ok&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;e&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="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="n"&gt;profile_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&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;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;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;profile_ids&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_001&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;id_002&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;id_003&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;id_004&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;id_005&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&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;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;process_profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;profile_ids&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;future&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;as_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;result&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;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep &lt;code&gt;max_workers&lt;/code&gt; at 3 to 5 for most workloads. Each open profile consumes a Chromium process — running 20 simultaneously on a standard machine will exhaust RAM and cause unstable behavior. For high-volume parallel automation, &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitBrowser's cloud phone feature&lt;/a&gt; offloads mobile profile execution to remote infrastructure, which removes the local resource constraint entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Approach Beats Stealth Patching
&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.amazonaws.com%2Fuploads%2Farticles%2Fxffqvvca7j0httmswn5w.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.amazonaws.com%2Fuploads%2Farticles%2Fxffqvvca7j0httmswn5w.png" alt=" " width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Standard Selenium stealth approaches work by patching the browser after it launches — injecting scripts that override &lt;code&gt;navigator.webdriver&lt;/code&gt;, spoofing canvas responses, and removing automation-specific Chrome flags. The problem is that these patches operate at the JavaScript layer, and detection systems that run before your scripts execute — at the TLS handshake level, or through browser binary fingerprinting — are not affected by them at all.&lt;/p&gt;

&lt;p&gt;BitBrowser's fingerprinting operates at the browser engine level, not through JavaScript injection. Canvas noise is applied in the rendering pipeline. WebGL parameters are spoofed at the driver level. &lt;code&gt;navigator.webdriver&lt;/code&gt; is absent because the browser was not launched by a standard WebDriver initialization — Selenium is connecting to a session that was already running.&lt;/p&gt;

&lt;p&gt;The result is that checks which reliably catch stealth-patched Selenium — like &lt;a href="https://abrahamjuliot.github.io/creepjs/" rel="noopener noreferrer"&gt;CreepJS&lt;/a&gt; or Cloudflare's bot management — behave differently against a properly configured BitBrowser session. The signals are consistent because they come from the profile's persistent configuration, not from per-session injection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Use Cases
&lt;/h2&gt;

&lt;p&gt;The BitBrowser + Selenium combination fits a specific category of automation work: scenarios where session persistence, fingerprint consistency, and &lt;a href="https://gotoproxy.com/2026/01/02/mobile-proxies-what-are-4g-5g-proxy-2026/" rel="noopener noreferrer"&gt;proxy isolation&lt;/a&gt; matter more than raw scraping speed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Account warming and management&lt;/strong&gt; — automating gradual engagement activity on freshly created accounts before running outreach or campaigns. The profile maintains session history between runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-account testing&lt;/strong&gt; — QA workflows where you need to verify how a feature behaves for users in different regions, device types, or account tiers. Each BitBrowser profile represents a distinct user environment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Competitor and market monitoring&lt;/strong&gt; — scraping platforms that use behavioral fingerprinting to detect and block scrapers. A persistent profile with a &lt;a href="https://tgproxy.io/2026/06/17/residential-proxies/" rel="noopener noreferrer"&gt;residential proxy&lt;/a&gt; and realistic session history bypasses most of these systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Affiliate and referral link testing&lt;/strong&gt; — verifying that tracking links, landing pages, and conversion flows work correctly across multiple independent browser environments. Isolated profiles ensure there is no cross-contamination between test sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; supports up to hundreds of profiles depending on the subscription tier, making it practical for both small teams running a handful of accounts and larger operations cycling through dozens of profiles in automated workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stability Considerations
&lt;/h2&gt;

&lt;p&gt;A few patterns that make this setup reliable in production rather than just in development:&lt;/p&gt;

&lt;p&gt;Always add a &lt;code&gt;time.sleep()&lt;/code&gt; after calling &lt;code&gt;open_profile&lt;/code&gt; before attempting to connect with Selenium. The profile needs a moment to fully initialize the Chromium process and expose the debugger port. Two seconds is usually sufficient; on slower machines, increase to four.&lt;/p&gt;

&lt;p&gt;Handle the case where a profile is already open. The BitBrowser API will return the existing debugger address if the profile is already running — your code should check for this rather than treating it as an error.&lt;/p&gt;

&lt;p&gt;Rotate profile usage to avoid behavioral patterns. If you are using profiles for account management, vary the time between sessions and the sequence of actions. The fingerprint isolation handles the device identity; you still need to avoid robotic behavioral patterns at the application layer.&lt;/p&gt;

&lt;p&gt;Keep BitBrowser and its bundled ChromeDriver version in sync. Selenium will throw version mismatch errors if your ChromeDriver is not compatible with the Chromium version BitBrowser uses. BitBrowser ships its own ChromeDriver — always point your Service path to that binary rather than a system-installed ChromeDriver.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tags: #selenium #webdev #automation #security&lt;/em&gt;&lt;/p&gt;

</description>
      <category>selenium</category>
      <category>webdev</category>
      <category>automation</category>
      <category>security</category>
    </item>
    <item>
      <title>How to Run Multiple Affiliate Accounts for Crypto Products (Binance, Bybit, OKX)</title>
      <dc:creator>Digital Growth Pro</dc:creator>
      <pubDate>Mon, 30 Mar 2026 21:14:24 +0000</pubDate>
      <link>https://dev.to/digitalgrowthpro/how-to-run-multiple-affiliate-accounts-for-crypto-products-binance-bybit-okx-341g</link>
      <guid>https://dev.to/digitalgrowthpro/how-to-run-multiple-affiliate-accounts-for-crypto-products-binance-bybit-okx-341g</guid>
      <description>&lt;p&gt;Managing affiliate accounts across multiple crypto exchanges is one of the most lucrative side strategies in the digital marketing space — but it is also one of the most technically fragile. Binance, Bybit, and OKX all operate sophisticated fraud detection systems that flag duplicate identities, shared device fingerprints, and suspicious referral patterns. One misstep and you lose not just one account, but your entire affiliate portfolio.&lt;/p&gt;

&lt;p&gt;This guide covers the technical setup and workflow that professional crypto affiliates use to run multiple accounts across exchanges — safely, efficiently, and in compliance with each platform's terms of service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Exchanges Flag Multi-Account Affiliates
&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.amazonaws.com%2Fuploads%2Farticles%2F3u74ujuitsqwi3ina7tz.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F3u74ujuitsqwi3ina7tz.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before diving into the setup, you need to understand what you are up against. Crypto exchanges invest heavily in fraud detection because referral abuse is a known attack vector — fake referrals, self-referrals, and circular referral schemes cost these platforms millions annually.&lt;/p&gt;

&lt;p&gt;The detection systems typically look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser fingerprinting&lt;/strong&gt; — canvas hash, WebGL renderer, audio context, and font list signatures that uniquely identify a device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP address patterns&lt;/strong&gt; — multiple accounts logging in from the same IP, especially residential or datacenter ranges&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cookie and LocalStorage cross-contamination&lt;/strong&gt; — shared session data between profiles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral patterns&lt;/strong&gt; — similar referral timing, same conversion funnels, identical onboarding flows across accounts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device metadata&lt;/strong&gt; — screen resolution, timezone, language settings, and hardware concurrency values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running multiple affiliate accounts from a standard browser — even in incognito mode — exposes nearly all of these signals. Incognito only clears cookies; it does nothing for fingerprint, IP, or hardware metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Technical Stack
&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.amazonaws.com%2Fuploads%2Farticles%2Fiaowvex4mpgvqyyvvsg7.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fiaowvex4mpgvqyyvvsg7.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A proper multi-account affiliate setup for crypto platforms requires three components working together:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Isolated Browser Profiles
&lt;/h3&gt;

&lt;p&gt;Each affiliate account needs its own isolated browser environment with a unique, consistent fingerprint. This means separate canvas hashes, different WebGL renderers, distinct font stacks, and independent cookie storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser&lt;/a&gt; is one of the more widely used tools for this. It lets you create hundreds of independent browser profiles, each with its own fingerprint configuration. Unlike running multiple separate browsers on one machine, each profile in BitBrowser is completely isolated — no shared memory, no shared cookies, no shared cache. You can assign each profile a custom user agent, screen resolution, timezone, and language to match the proxy you pair it with.&lt;/p&gt;

&lt;p&gt;What makes this relevant for crypto affiliates specifically is the consistency factor. When you log into Binance or OKX's affiliate portal over several weeks, the platform expects to see the same browser fingerprint each time. BitBrowser stores the profile state persistently, so your "Binance affiliate account A" always looks like the same device — because from the exchange's perspective, it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Residential or Mobile Proxies
&lt;/h3&gt;

&lt;p&gt;Each browser profile needs its own dedicated proxy — ideally residential or mobile IP. Datacenter IPs are increasingly flagged by crypto exchanges, particularly at the KYC verification stage.&lt;/p&gt;

&lt;p&gt;Assign one proxy per profile and keep that assignment permanent. Avoid rotating proxies on profiles that are already active — a sudden IP change on an established affiliate account triggers the same red flags as a regular user account.&lt;/p&gt;

&lt;p&gt;Match the proxy's geolocation to the account's registered country and timezone settings in the browser profile. If your proxy is in Germany, the browser timezone should be Europe/Berlin, the language should reflect that region, and the KYC documents (if applicable) should correspond.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Separate Identity Infrastructure
&lt;/h3&gt;

&lt;p&gt;Each affiliate account on Binance, Bybit, or OKX requires a unique verified identity. This means separate email addresses, phone numbers, and — where required — distinct KYC documentation. This is non-negotiable from a terms of service standpoint, and it is also the only way to make the technical isolation meaningful. You cannot run legitimate separate affiliate accounts under a single identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up the Workflow
&lt;/h2&gt;

&lt;p&gt;Here is how a typical setup looks in practice for managing three to five crypto affiliate accounts simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Create profiles in BitBrowser&lt;/strong&gt;&lt;br&gt;
Set up one profile per exchange account. Configure the fingerprint settings — pay particular attention to canvas noise, WebGL vendor string, and hardware concurrency (number of CPU threads). These are the three most commonly checked parameters by exchange detection systems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Assign proxies&lt;/strong&gt;&lt;br&gt;
Pair each profile with a dedicated residential proxy. Note the IP, city, and timezone, and configure the profile's browser settings to match exactly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Register affiliate accounts&lt;/strong&gt;&lt;br&gt;
Open each profile, navigate to the exchange's affiliate registration page, and complete the registration process using that profile exclusively. Do not switch between profiles mid-session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Verify and warm up&lt;/strong&gt;&lt;br&gt;
After registration, spend one to two weeks using each account normally before aggressively promoting your affiliate links. Exchanges track behavioral patterns over time — accounts that go from zero activity to high referral volume instantly are more likely to be reviewed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Organize your dashboard&lt;/strong&gt;&lt;br&gt;
BitBrowser allows you to label and group profiles. Create a naming convention — for example &lt;code&gt;[Exchange]-[Niche]-[Profile Number]&lt;/code&gt; — so you can quickly identify which profile corresponds to which affiliate account across your different crypto platforms.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing Mobile-Based Affiliate Campaigns
&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.amazonaws.com%2Fuploads%2Farticles%2Fauuvawpbe9hnf3shmrov.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fauuvawpbe9hnf3shmrov.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;br&gt;
A growing number of crypto affiliate campaigns are mobile-first. Bybit in particular has seen strong referral growth through mobile app installs, and TikTok-driven crypto affiliate funnels almost exclusively target mobile users.&lt;/p&gt;

&lt;p&gt;For affiliates running mobile-side campaigns alongside browser-based ones, &lt;a href="https://www.bitbrowser.net/cloudphone" rel="noopener noreferrer"&gt;BitCloudPhone&lt;/a&gt; addresses a specific gap. It provides cloud-based Android environments — essentially virtual phones — that run independently in the cloud without requiring physical devices. Each cloud phone instance has its own device fingerprint, IMEI, and app data isolation.&lt;/p&gt;

&lt;p&gt;This matters for crypto affiliate work because exchanges like OKX and Binance track app-level device identifiers separately from browser fingerprints. Running a mobile campaign from the same physical device as your browser-based campaigns can create cross-signal correlations that flag your accounts. Cloud phones eliminate that overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes That Lead to Bans
&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.amazonaws.com%2Fuploads%2Farticles%2F9xtlko571b7qw687dx6t.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2F9xtlko571b7qw687dx6t.jpg" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even with the right tools, there are operational mistakes that consistently get crypto affiliates flagged:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logging into multiple profiles from the same network without proxies.&lt;/strong&gt; Your home router's IP becomes the common denominator across all accounts. Always ensure proxies are active before launching any profile session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reusing referral links across profiles.&lt;/strong&gt; Each affiliate account generates its own referral links. Using account A's referral link while logged into account B creates a direct association between the two profiles in the exchange's backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using the same payment wallet for multiple affiliate payouts.&lt;/strong&gt; Exchanges monitor payout addresses. Multiple affiliate accounts pointing to the same withdrawal wallet is one of the most reliable ways to trigger a review — regardless of how clean your browser setup is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inconsistent activity patterns.&lt;/strong&gt; An affiliate account that only logs in when referral commissions are available — never browsing the exchange, never checking market data — has a very different behavioral pattern from a legitimate user. Build natural activity into each profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scaling Beyond Five Accounts
&lt;/h2&gt;

&lt;p&gt;Once your initial setup is stable and each account is generating consistent referral activity, scaling becomes primarily an operational challenge rather than a technical one.&lt;/p&gt;

&lt;p&gt;BitBrowser supports team collaboration features that allow you to share profile access with team members without exposing login credentials directly. This is useful if you are working with a small team managing different traffic sources — one person handles SEO-driven referrals, another manages social media campaigns, and each operates within their assigned profiles.&lt;/p&gt;

&lt;p&gt;The key metric to track as you scale is referral quality, not volume. Binance, OKX, and Bybit all have affiliate quality scoring systems. Accounts with high registration-to-KYC conversion rates, active trading referrals, and low fraud-flag rates get better commission tiers and less scrutiny. Build your campaigns around genuine audiences rather than bulk traffic, and the technical isolation work you have done will compound into long-term affiliate income.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Running multiple affiliate accounts across crypto exchanges is technically achievable and commercially viable — but only if the underlying infrastructure is solid. The combination of isolated browser profiles, dedicated proxies, and clean identity separation creates the environment where each account can operate as a genuinely independent entity.&lt;/p&gt;

&lt;p&gt;The exchanges are not trying to prevent affiliates from scaling. They are trying to prevent fraud. If your setup is technically clean and your referrals are legitimate, the same systems designed to catch bad actors will simply pass over you.&lt;/p&gt;

&lt;p&gt;Start with two or three accounts, stabilize your workflow, then expand. If you are new to the browser profile approach, &lt;a href="https://client.bitbrowser.cn/register?lang=en&amp;amp;code=bit2H44" rel="noopener noreferrer"&gt;BitBrowser's free plan&lt;/a&gt; gives you enough profiles to test the setup before committing to a paid tier.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>cryptocurrency</category>
      <category>browserautomation</category>
    </item>
  </channel>
</rss>
