All posts

Chrome DevTools Protocol: connect to remote CDP

What is the chrome devtools protocol? JSON commands over a WebSocket, routed by sessionId. Learn to connect Playwright or Puppeteer to a remote endpoint.

  • chrome devtools protocol
  • cdp
  • playwright
  • puppeteer
  • websocket
On this page

The Chrome DevTools Protocol (CDP) is the interface Chrome DevTools uses to instrument, inspect, debug and profile Chrome: methods and events grouped into domains, reachable over a WebSocket. Puppeteer, Playwright and Selenium all drive Chrome through it. Treat it as unstable: Selenium says CDP isn’t designed to have a stable API, so behavior depends on the browser version.

Your script works against a local Chrome, then breaks once the endpoint sits on a remote host. The connection is refused, or the socket opens and nothing answers. The code and the protocol are the same. Only the network path changed.

After reading, you can send and read raw CDP messages, connect Playwright, Puppeteer or a raw socket to a remote endpoint, and pick methods that are not deprecated.

Key Takeaways

  • Target.attachToTarget returns the sessionId, while Target.createBrowserContext creates an isolated context. They are different objects.
  • In new code, intercept requests with the Fetch domain, not Network.setRequestInterception.
  • Wayfern starts the session when the socket opens; a cold start takes about 45 to 50 seconds.
  • Wayfern has no persistent profiles or cookie import and export, so your code saves and restores cookies and storage.

What is the Chrome DevTools Protocol?

The Chrome DevTools Protocol (CDP) is a set of commands and events, grouped into domains, that tools send as JSON over a WebSocket to instrument, inspect, debug and profile Chromium-based browsers. That is how Chrome’s Protocol monitor documentation frames it.

Image generated

The devtools-frontend docs define a domain as “A group of CDP methods and events, which can usually be individually enabled or disabled and individually supported by different types of targets.” Targets are things like pages and workers. Domains are what you call on them.

You can see the local baseline yourself. Start Chrome with --remote-debugging-port=9222, then open http://localhost:9222/json/version.

The JSON includes a webSocketDebuggerUrl field: the browser-level endpoint your client connects to. /json/list returns the open pages, each with its own socket URL.

The switch’s behavior has changed between Chrome releases, so check Chrome for Developers before you script it.

Browse the domains in the protocol viewer. An experimental label warns that the shape may change. A deprecated label means you should find the replacement before you build on it.

The Protocol monitor panel’s setup has also changed. Follow the same Protocol monitor documentation on developer.chrome.com for the current enabling steps.

How does a Chrome DevTools Protocol connection work?

A Chrome DevTools Protocol connection is one WebSocket carrying JSON commands, responses and events, where a sessionId routes each command to the right target.

  1. Open the browser-level WebSocket. Use the ws or wss URL for the browser, not a single page, so you can reach every target. A page-level URL binds you to one tab.
  2. List or create a target. Send Target.getTargets to see existing ones, or Target.createTarget with a url to open a new one. You get back a targetId.
  3. Attach to it. The protocol’s Target domain definition describes Target.attachToTarget as “Attaches to the target with given id.” Pass flatten: true. The response holds the sessionId you need.
  4. Send commands with that sessionId. Call Page.enable first if you want that domain’s events.

Here is a command and its result. The protocol’s Page domain definition describes Page.captureScreenshot as “Capture page screenshot.”

{"id": 1, "method": "Page.captureScreenshot", "params": {"format": "png"}, "sessionId": "SESSION_ID"}

The reply carries the same id and either result or error:

{"id": 1, "result": {"data": "iVBORw0KGgo..."}, "sessionId": "SESSION_ID"}
{"id": 1, "error": {"code": -32601, "message": "'Foo.bar' wasn't found"}}

Events have no id. They arrive whenever the browser has something to report, so match replies by id and treat every other message as an event:

{"method": "Page.loadEventFired", "params": {"timestamp": 1234.5}, "sessionId": "SESSION_ID"}

Target.createBrowserContext is not a session request. The Target domain definition says it “Creates a new empty BrowserContext.”

It returns a browserContextId, which you pass to Target.createTarget so the new tab lands in that isolated context. You still call Target.attachToTarget afterward to get a sessionId.

A working connection returns a sessionId string in step 3, then a response with data for your screenshot call.

How do you connect to a remote CDP endpoint?

You connect to a remote CDP endpoint by handing its wss URL to your client’s attach method instead of launching a local browser. The protocol stays the same.

  1. Get a link token. Mint one under your project’s Browser links and store it as WF_TOKEN. Never hard-code it.
  2. Build the URL. Use wss://api.wayfern.com/ws?token= plus the token. Opening the socket starts the session, so there is no create-session REST call (see the Wayfern API reference). The first message is a JSON connected message with a connectionId, then plain CDP.
  3. Raise your connect timeout. A cold start takes about 45 to 50 seconds, longer with a residential exit. A short default timeout fails before the browser is ready.
  4. Attach with Playwright. Wayfern runs Chromium only, so connectOverCDP() is the path. Playwright’s BrowserType docs say this connection has “significantly lower fidelity than the Playwright protocol connection”, so expect some advanced features to differ.
import { chromium } from "playwright";
const browser = await chromium.connectOverCDP(
  `wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}`,
  { timeout: 90_000 },
);
const page = await browser.contexts()[0].newPage();
await page.goto("https://example.com");
  1. Attach with Puppeteer. Pass the same URL as browserWSEndpoint to puppeteer.connect(), as Puppeteer’s ConnectOptions reference documents.
import puppeteer from "puppeteer-core";
const browser = await puppeteer.connect({
  browserWSEndpoint: `wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}`,
});
const page = await browser.newPage();
await page.goto("https://example.com");
  1. Or skip the library. A raw client shows the id/method/params shape directly.
import WebSocket from "ws";
const ws = new WebSocket(`wss://api.wayfern.com/ws?token=${process.env.WF_TOKEN}`);
ws.on("message", (data) => {
  const msg = JSON.parse(data);
  if (msg.connectionId) {
    ws.send(JSON.stringify({ id: 1, method: "Target.getTargets" }));
  } else if (msg.id === 1) {
    console.log(msg.result.targetInfos);
  }
});

Success looks like a page that loads and a targetInfos array printed from the raw client.

For exits, viewport and other session options, see our guide to anti-detect browser sessions over CDP. Copy the Playwright script above, add your link token, and run it to try a hosted session.

Which Chrome DevTools Protocol methods are current?

The protocol viewer is the authority on which Chrome DevTools Protocol methods are current, so read each method’s status label there before you adopt it. The devtools-protocol repository points readers to it: “Use the protocol viewer for navigating the protocol.”

The methods below are the working set for a remote session. Their status labels can change between Chromium releases, so check the viewer for each one instead of trusting a copied label.

  1. Target.attachToTarget: attaches to a target and returns the sessionId that goes on every later command for it.
  2. Target.createBrowserContext: creates an isolated browser context, a separate object from the session.
  3. Page.navigate: loads a URL in the attached page.
  4. Page.captureScreenshot: returns the rendered page as image data.
  5. Network.setCookie: sets one cookie. Use it to restore cookies your own code saved on a previous run.
  6. Emulation.setDeviceMetricsOverride: per the Emulation domain definition, it “Overrides the values of device screen dimensions”. It doesn’t resize the real window, so outerWidth, outerHeight and screen.* can disagree with the overridden values.
  7. Fetch: the interception path. Call Fetch.enable, handle each Fetch.requestPaused event, then answer with Fetch.continueRequest, Fetch.fulfillRequest or Fetch.failRequest.

Interception: use Fetch, not Network

Leave Network.setRequestInterception out of new code and use Fetch.

Build request blocking, header rewriting and mocked responses on the Fetch domain instead.

Is the Chrome DevTools Protocol stable, and what is WebDriver BiDi?

The Chrome DevTools Protocol is not a stable public API: it changes with each Chromium release, third parties get no stability guarantee, and WebDriver BiDi is the standards-track alternative.

Selenium’s CDP documentation puts the caveat plainly: CDP “is not designed for testing, nor to have a stable API, so functionality is highly dependent on the version of the browser.”

CriterionCDPWebDriver BiDi
Built forInstrumenting and debugging ChromeBrowser automation
Stability promiseNone for third partiesStated aim of a stable API
Browser scopeTied to the browser versionStated aim of cross-browser

The Chrome DevTools docs say DevTools uses CDP “to instrument, inspect, debug and profile Chrome browsers.” Automation tools borrowed a debugging interface, which is why its promises are weaker than a testing standard’s.

Selenium’s BiDi documentation describes WebDriver BiDi as the W3C standard bidirectional protocol, meant as a stable, cross-browser API for browser automation.

That sentence states an aim. Check each browser’s support before you depend on it.

Selenium’s CDP page also carries the line “CDP support is temporary until WebDriver BiDi has been implemented.” That describes Selenium’s own roadmap. Code that connects to a remote CDP endpoint still sends CDP.

Two habits follow:

  • Pin the Chromium version you drive. A script tested on one release can fail on the next because a method changed.
  • Re-read method status labels on every upgrade. Check each method in the protocol viewer before you rely on it, and expect labels to change between releases.

Choose CDP for Chromium-only work that needs network, emulation or tracing control. Choose WebDriver BiDi for tests that must run across browsers.

What fails when a CDP browser runs on a hosted service?

A CDP browser on a hosted service fails in four ways a local Chrome never does: a slow first connect, a connection cap, a refused location, and no saved state.

Cold start. The first connect waits for a browser to start; step 3 of the connection walk-through gives Wayfern’s timing. Set your connect timeout well past a minute, or your script will give up on a healthy session.

Connection cap. Each link token has a connection cap of 1 to 100, and the default is 10, according to the limits page. Size your worker pool to the cap before you fan out.

A refused location. Send an unknown country, region, city or isp and the socket closes with code 4007, as Wayfern’s CDP sessions page documents.

We don’t fall back to a nearby exit, because a silent fallback would put your traffic in the wrong place. Codes are checked against a live catalog.

No saved state. The honest catch: Wayfern has no persistent profiles and no cookie import or export, as our Multilogin comparison also states.

Cookies and storage stay live while the socket is open. A pooled instance is torn down when the last connection closes.

So you save and restore state in your own code:

  1. At the end of a run, send Network.getCookies and write the result to your own storage.
  2. At the start of the next run, send Network.setCookie once per saved cookie, before your first page.goto().
  3. Hold one sticky session id so the cookies return from the same residential IP.

Selenium’s CDP page has examples of working with the protocol, including cookie handling. Our authenticated scraping guide covers the sign-in side.

CDP emulation or a real window and exit: which holds up?

For location and viewport, a real exit and a real window give a page one consistent story, while CDP emulation overrides single values.

CDP emulationReal window and exit
Location signalEmulation.setGeolocationOverrideResidential exit by country, region, city or isp
IP addressUnchangedFollows the exit; session id holds one IP
ViewportEmulation.setDeviceMetricsOverridewidth and height on the /ws URL
Device presentedWhatever you overrideDesktop only
Consistent valuesThe ones you overrideouterWidth/outerHeight, screen.*

Emulation also carries the version dependence described in the stability section, because every override is a CDP method.

Wayfern’s residential exits work at the network layer: you choose by country (ISO-3166), region, city or isp, and a session id holds one IP for a whole flow.

Viewport works the same way. Wayfern takes width and height on the /ws URL, 500 to 3600 by 400 to 1900 CSS pixels, and resizes the real window, per the Wayfern API reference.

Wayfern’s API reference says the real window is resized and nothing is emulated, so outerWidth/outerHeight and screen.* stay consistent.

The limit is plain: Wayfern presents a desktop device and offers no phone sizes. If you need mobile device emulation, use CDP emulation in a browser you run yourself.

  • CDP emulation: best for quick checks on a local Chrome, or for mobile layouts.
  • Real window and exit: best for desktop sessions where the IP, location and window metrics have to agree.

Frequently asked questions

Is the Chrome DevTools Protocol stable?

No guarantee exists for third parties. Selenium’s documentation says CDP isn’t designed “to have a stable API, so functionality is highly dependent on the version of the browser.” Pin the browser version you drive, and check a method’s status label in the protocol viewer before you depend on it.

How do I enable remote debugging in Chrome?

Start Chrome with the --remote-debugging-port=9222 flag, then read the WebSocket URL from http://localhost:9222/json/version. Pass that URL to your client.

Can Playwright connect to a browser that is already running?

Yes, with chromium.connectOverCDP() and the endpoint URL. Playwright calls this connection “significantly lower fidelity” than its own protocol, so test any advanced feature before you rely on it.

Is Network.setRequestInterception still usable, and what replaces it?

Leave it out of new code and use the Fetch domain instead: call Fetch.enable, handle Fetch.requestPaused, then answer with Fetch.continueRequest or Fetch.fulfillRequest.

Do I need a REST call to start a Wayfern browser session?

No. Opening the wss socket starts the session, and the first message is a JSON “connected” message with a connectionId, followed by plain CDP. See CDP sessions on Wayfern.

What should you do next with CDP?

Today: Open the Protocol monitor in Chrome DevTools and send one raw command to your local Chrome. Read the reply’s result or error object, then look up that method’s status label in the protocol viewer.

This week: Point Playwright or Puppeteer at a remote wss endpoint. Expect a slow first connect and write your timeout around it.

Before production:

  • Check each method’s status label.
  • Save and restore cookies and storage in your own code.
  • Set the connection cap on your link token.

Ready to run CDP against a hosted browser? Get a link token and connect with the Wayfern quickstart. The pricing page lists the credit rate per open-page hour. At zero credits, work stops; nothing runs on credit you did not buy.

Sources

Protocol monitor: View and send CDP requests - Chrome… · devtools-frontend/docs/devtools-protocol.md at main - GitHub · Mastering the Chrome DevTools Protocol (CDP): A Deep Dive… · Chrome DevTools Protocol - Selenium · Chrome DevTools Protocol - Target domain · Anti-Detect Browser Sessions over CDP | Wayfern · Wayfern API reference · Anti-Detect Browser Sessions over CDP | Wayfern. 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.