Websocket browser automation service: How to pick
What is a websocket browser automation service? A hosted browser you drive over one CDP WebSocket. Learn to connect Playwright, handle failures, pick a vendor.
- websocket
- browser automation
- chrome devtools protocol
- playwright
- puppeteer
On this page
- What is a websocket browser automation service?
- How does a CDP client use one WebSocket?
- How do you connect Playwright and Puppeteer to a hosted browser over WebSocket?
- What happens when a websocket browser automation service session fails?
- How do tokens, exits and locations work in a socket URL?
- How do you keep state consistent in a websocket browser automation service?
- Which websocket browser automation service fits your job?
- Frequently asked questions
- Can Playwright connect to Firefox or WebKit through a WebSocket service?
- Do I need an SDK to use a hosted CDP endpoint?
- Is WebDriver BiDi a replacement for CDP?
- What does WebSocket close code 4007 mean?
- What to do next with a websocket browser endpoint
- Sources
A websocket browser automation service is a hosted browser you drive over one WebSocket carrying Chrome DevTools Protocol (CDP) JSON. The term covers a local relay or extension bridge and an agent API that takes natural-language commands, but the hosted CDP endpoint is the one you drive with code.
Your Playwright or Puppeteer script works on your laptop, and you want it to run somewhere else. Search results for a websocket browser automation service mix hosted CDP endpoints, local relays and agent APIs.
After reading, you can tell the three apart, connect a client, handle failed connections and token problems, and pick a vendor. We operate Wayfern’s hosted anti-detect Chromium, so the examples use that endpoint.
Key Takeaways
- On Wayfern, opening the socket starts the session; the first message is a JSON
connectedmessage withconnectionId.- An unknown country or city gets close code 4007 (private-use range, RFC 6455) instead of a different exit.
- Browser time costs 8 credits per open-page hour, metered per second with a 60-second minimum; zero credits closes open sessions.
- A cold start takes 45 to 50 seconds, and profiles don’t persist, so save cookies and storage yourself.
What is a websocket browser automation service?
A websocket browser automation service runs the browser, the proxy and the session lifecycle for you, so your code needs only an endpoint URL and a token. Your script opens the socket, sends Chrome DevTools Protocol commands as JSON, and receives responses and events.

Browserless’s Browsers as a Service docs describe this model for its v2 service. It “lets you connect Puppeteer or Playwright to managed browsers over WebSocket”, and you run existing code by changing the connection URL.
A local relay or extension bridge is a second meaning. The Hermes Browser Extension repo describes a Manifest V3 Chrome/Edge extension paired with a Python WebSocket relay server.
A CLI client sends commands to the relay, and the relay forwards them to the extension in your own browser, so your logins stay on your machine.
An agent service is the third meaning. The mirvoism/browser-automation-service README presents an API that can “Transform natural language commands into browser automation via REST API”.
Pick a websocket browser automation service by who drives the browser:
- A hosted CDP endpoint fits when code is the operator.
- A relay fits when your own browser session is the point.
- An agent service fits when you want results rather than control.
How does a CDP client use one WebSocket?
A CDP client opens one WebSocket to the browser, then sends and receives JSON messages over it until the socket closes. RFC 6455 defines the transport underneath: “The protocol consists of an opening handshake followed by basic message framing, layered over TCP.”
On Wayfern, that handshake starts the session. According to Wayfern’s API reference, opening wss://api.wayfern.com/ws?token=LINK_TOKEN creates it, with no create-session REST call and no queue to poll.
The first message you receive is a JSON connected message that includes connectionId. Everything after it is plain CDP, so any CDP client works.
A command is a JSON object with id, method and params.
{"id": 1, "method": "Page.navigate", "params": {"url": "https://example.com"}} The response repeats the same id, which is how you match it to the command. The frameId value differs on every run:
{"id": 1, "result": {"frameId": "F1"}} Events such as Page.loadEventFired arrive on their own. They carry a method and params but no id, because no command asked for them.
Playwright and Puppeteer do this matching for you, and the same three shapes still cross the wire.
How do you connect Playwright and Puppeteer to a hosted browser over WebSocket?
Playwright and Puppeteer both attach to a hosted browser by opening one WebSocket to a CDP endpoint, and on Wayfern that endpoint is wss://api.wayfern.com/ws.
Puppeteer’s puppeteer.connect() attaches to an existing browser through browserWSEndpoint. Playwright’s connectOverCDP() attaches over CDP too, and Playwright’s BrowserType docs say this is only supported for Chromium-based browsers.
Playwright’s own protocol, reached through browserType.connect(), is a separate path. A CDP client cannot drive Firefox or WebKit.
- Export the token. Never put it in source:
export WF_TOKEN="your_link_token" - Connect from Playwright (JS). The first call includes the cold start, so raise the timeout:
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP(
`wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}`,
{ timeout: 120_000 },
);
const page = await browser.contexts()[0].newPage();
await page.goto("https://example.com");
console.log(await page.title());
await browser.close(); - Connect from Playwright (Python, sync form):
import os
from playwright.sync_api import sync_playwright
url = f"wss://api.wayfern.com/ws?token={os.environ['WF_TOKEN']}"
with sync_playwright() as p:
browser = p.chromium.connect_over_cdp(url, timeout=120_000)
page = browser.contexts[0].new_page()
page.goto("https://example.com")
print(page.title())
browser.close() - Connect from Puppeteer:
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}`,
protocolTimeout: 120_000,
});
const page = await browser.newPage();
await page.goto("https://example.com");
console.log(await page.title());
await browser.disconnect(); Puppeteer’s docs say browser.disconnect() drops the connection and leaves the browser process running. Call it here: the hosted instance goes away when its last connection closes, so you don’t need browser.close().
Success is a printed page title from your websocket browser automation service.
Don’t add a fixed sleep after goto(). Playwright’s Page docs say “Never wait for timeout in production” and point to actions that wait automatically.
CDP isn’t the only WebSocket protocol. The W3C WebDriver BiDi specification defines a WebSocket-based one, and Puppeteer documents BiDi support with a list of its limits against CDP. Wayfern’s endpoint speaks CDP.
Get a link token from the Wayfern docs, run the first snippet, and read how anti-detect browser sessions over CDP handle exits.
What happens when a websocket browser automation service session fails?
When a browser WebSocket session closes or fails, your client receives a close event with a numeric code, and the hosted instance is torn down after its last connection ends.
A refused connection ends with a close code. Wayfern’s CDP sessions page states that “an unknown location is refused with close code 4007 instead of falling back to the wrong place.” RFC 6455 says codes in the 4000 to 4999 range are “reserved for private use,” so 4007 is Wayfern’s own convention.
Read the close code list for your websocket browser automation service before you write error handling.
Plan concurrency and API rate limits are on /docs/limits. Check that page for idle and maximum session durations before you plan a long job.
Wayfern’s API reference says a pooled instance is reused only for the same link token, workload and exit. It is torn down when the last connection closes, and any cookies or storage inside it go with it.
On reconnect, follow this order:
- Read the close event first. MDN documents that
wasCleanis true when the connection closed cleanly, and the event also carriescodeandreason. - Don’t retry a refusal such as 4007. It fails the same way every time.
- Retry a dropped network connection with exponential backoff.
- Restore cookies and storage from your own saved copy, because the old instance is gone.
The socket’s lifetime is also the meter. On Wayfern’s pricing page, browser time is 8 credits per open-page hour, metered per second, with a 60-second minimum per page.
At zero credits, calls are refused and open sessions are closed, and auto top-up is off by default.
How do tokens, exits and locations work in a socket URL?
A Wayfern socket URL carries the entire connection: the token authorizes it, and query parameters choose the exit and the window size.
You mint a link token per project under “Browser links”. Wayfern’s limits docs say it has a connection cap from 1 to 100 (default 10), an optional expiry, and it can be revoked. Per the API reference, Wayfern’s wf_ API tokens are scoped, shown once and stored hashed.
A websocket browser automation service URL ends up in logs and error traces, so treat it as sensitive. Keep the token in an environment variable or secret store, and mint short-lived tokens for CI.
For a residential exit, add country (ISO-3166), region, city or isp. A session id holds one residential IP across a whole flow.
wss://api.wayfern.com/ws?token=LINK_TOKEN&country=DE&city=berlin&session=checkout42 Location codes are validated strictly. Wayfern’s CDP session use case says Wayfern checks every code against a live catalog (GET /api/proxy/locations) before the browser is handed over.
If you request country=ZZ, which isn’t in the catalog, the socket closes with 4007 and your script fails at connect time. A silent fallback would give you a browser in some other country, and a geo test would pass against the wrong market.
You can also bring your own proxy. Save an http, https or socks5 proxy on a link with proxyMode set to own, then pick it per connection with proxy=own, residential or none. The credentials stay out of the URL, and the password is encrypted at rest.
wss://api.wayfern.com/ws?token=LINK_TOKEN&proxy=own&width=1440&height=900 The viewport is set on the same URL with width and height.
For the full parameter list, see the developer docs.
How do you keep state consistent in a websocket browser automation service?
Let the browser own its fingerprint and window. Save cookies and storage in your own code, then restore them on the same exit.
On Wayfern, the identity comes from its own Chromium build. Detection is an arms race, and this mechanism guarantees no outcome.
- Pin the exit. Pass the same
sessionid (and the samecountry) on every connection in one flow. A cookie issued to one IP and replayed from another is an easy signal for a site to read. - Save state before you disconnect. Playwright’s
storageState()returns the storage state of a context, including its cookies. Write it to a file you control and keep that file secret, because it holds live session cookies. - Restore state on the next run. Pass the saved state to
newContext(), and connect through the samesessionid so the cookies meet the exit they came from. - Check the result. The first page you open should load as logged in, with no second login.
import { chromium } from "playwright";
import fs from "node:fs";
const url = `wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}&country=US&session=run42`;
const browser = await chromium.connectOverCDP(url);
const context = await browser.newContext(
fs.existsSync("state.json") ? { storageState: "state.json" } : {},
);
const page = await context.newPage();
await page.goto("https://example.com/account");
// ... your steps ...
await context.storageState({ path: "state.json" });
await browser.close(); As our Wayfern vs Multilogin comparison notes, Wayfern has no named profiles, no cookie import or export, and no per-vector fingerprint switches. If you need that control, a desktop profile manager such as Multilogin fits better.
Use this for testing, research, monitoring, public-data collection and logins you’re entitled to use. Our authenticated scraping guide covers the login side.
Which websocket browser automation service fits your job?
The right websocket browser automation service depends on the engine you need, where state must live and whether code or a person drives the browser.
| Wayfern | Browserless | 2Captcha Browser API | |
|---|---|---|---|
| Client libraries | Playwright, Puppeteer, any CDP client | Puppeteer or Playwright | Playwright, Puppeteer, any CDP client |
| Where the credential comes from | Link token in the URL query | API token from the account dashboard | Ready-made URL from the dashboard |
| Transport | CDP over WebSocket | WebSocket (CDP) | CDP over WebSocket |
Wayfern runs Chromium only. Its CDP sessions documentation states that “a cold start takes about 45 to 50 seconds, longer with a residential exit.”
Wayfern’s Multilogin comparison adds that it has “no customer-facing persistent profiles at all”: you save and restore cookies in your own code.
For Browserless, a third-party Browserless summary says it exposes headless Chrome, Firefox and WebKit, so it covers engines Wayfern can’t.
2Captcha’s docs describe its Browser API as a cloud browser for sites that need “CAPTCHA handling” among other jobs. You connect with “a ready-made CDP WebSocket URL from your 2Captcha dashboard.”
A desktop profile manager is a different product, because a person drives it. The same Wayfern comparison says: “If hand-managing identities is the job, Multilogin is doing that job and Wayfern is not.”
- Browserless fits when you need Firefox or WebKit, or your code already targets its connection model.
- 2Captcha’s Browser API fits when captcha handling is central to the job.
- Multilogin fits when a person manages many long-lived profiles by hand.
- Wayfern fits when code drives short Chromium sessions and you want city-level residential exits and strict location validation.
Frequently asked questions
Can Playwright connect to Firefox or WebKit through a WebSocket service?
Not through CDP. Playwright’s connectOverCDP() attaches to Chromium-based browsers only. Playwright drives Firefox and WebKit through its own protocol, which a plain CDP-over-WebSocket client doesn’t speak. For those engines, pick a service that exposes Playwright’s native connection instead of a CDP endpoint.
Do I need an SDK to use a hosted CDP endpoint?
No SDK is required. Playwright, Puppeteer or any client that speaks CDP connects straight to the wss URL. Browserless describes moving a script as changing the connection URL, and 2Captcha’s Browser API works the same way.
Is WebDriver BiDi a replacement for CDP?
Not for hosted browsers today. BiDi is a W3C specification for two-way, cross-browser automation, and Puppeteer can drive browsers over it. CDP is Chromium’s own protocol, and the endpoints above speak it, so check Puppeteer’s BiDi documentation before you move a script.
What does WebSocket close code 4007 mean?
Code 4007 is application-defined. RFC 6455 reserves 4000 to 4999 for private use, so each service assigns its own meanings. On Wayfern, 4007 means you asked for a location it doesn’t know, and it refuses the connection instead of falling back to another exit, as its page on CDP sessions with city and ISP targeting explains.
What to do next with a websocket browser endpoint
Start by deciding which kind of websocket browser automation service your job needs: a hosted CDP endpoint, a local relay, or an agent API. If you pick a hosted endpoint, read its list of close codes before you write any code.
Within the week, run your existing Playwright or Puppeteer script against a hosted endpoint. Keep the token in an environment variable, and set connection and navigation timeouts above the cold start.
Before production:
- Add reconnect handling that reads the close code and retries only when the code says a retry can work.
- Write your own save and restore for cookies and storage, and reuse them only on the same exit.
Start with the Wayfern docs quickstart and connect a first session with a link token. Budget before you begin: Wayfern’s pricing page lists the Developer plan and the credit packs, the cheapest self-serve options. Treat the first session as a paid test of your own script.
Sources
Browsers as a Service | Browserless Documentation · GitHub - mindfoldz0-spec/hermes-browser-extension: Browser… · Anti-Detect Browser Sessions over CDP | Wayfern · RFC 6455: The WebSocket Protocol · Wayfern pricing · Wayfern API reference · BrowserType | Playwright · Wayfern vs Multilogin: anti-detect browser comparison. Every other source is linked where the article cites it.
See us more often in Google
One click marks Wayfern as a preferred source in Google, so our posts can show up more often in your Top Stories, AI Mode and AI Overviews.