// Use case · Browser sessions
For jobs that need a real browser session: log in, click through steps and hold one exit IP, without running a browser fleet. Your driver, our browser.
credits per open-page hour
8
connections per link token
1–100
exit targeting
City, ISP
// First message on the socket, then plain CDP{ "type": "connected", "connectionId": "0c2c3a1e-…", "instanceId": "inst-…", "contextId": "ctx-…"} // Why
The problem
What Wayfern does
chromium.connectOverCDP or puppeteer.connect. The session starts when the socket opens: no SDK, no create call.session id to keep one IP across a flow.// How it works
There is no SDK and no session API to learn. From the connect call onward it is the Playwright or Puppeteer you already write.
Step 1 of 4
In a project, mint a link token. Set its connection cap (1 to 100, default 10) and an optional expiry.
Project → Links → New link
Step 2 of 4
Open the CDP WebSocket with the token in the query string. Playwright and Puppeteer attach as they would to a local browser.
wss://api.wayfern.com/ws
Step 3 of 4
Add country, region, city or isp for a residential exit and session to keep one IP. Leave them off for the free datacenter exit.
?country=US&session=run42
Step 4 of 4
Everything after connect is your usual script. Billing runs per open page and stops when the socket closes.
8 credits / open-page hour
// Code
The same login flow in each client. Exit selectors ride on the connection URL.
import { chromium } from "playwright";
// The session is created when this socket opens: there is no REST call to
// "start" one, and no queue to poll.
const browser = await chromium.connectOverCDP(
"wss://api.wayfern.com/ws?token=<LINK_TOKEN>&country=US®ion=CA&session=run42",
);
const page = await browser.contexts()[0].newPage();
// Everything below is ordinary Playwright, on a browser you hold open.
await page.goto("https://example.com/login");
await page.fill("#email", process.env.ACCOUNT_EMAIL);
await page.fill("#password", process.env.ACCOUNT_PASSWORD);
await page.click("button[type=submit]");
await page.waitForSelector("[data-testid=dashboard]");
console.log(await page.title());
await browser.close();// Build or buy
A scraping endpoint hands you the page it fetched. A browser lets you log in, fill a form and wait for what you came for. The difference is structural.
| Capability | Scraping API | Self-hosted browsers | Wayfern |
|---|---|---|---|
| Interaction | One request in, one response out | Full CDP, once you run the browsers | Full CDP over one WebSocket: goto, fill, click, wait |
| Login state | Ends with the response | Yours to keep alive | Cookies and storage stay live while the socket is open |
| Isolation | Not exposed to the caller | Yours to build and enforce | A pooled instance is reused only for the same link token, workload and exit |
| Exit location | Whatever the vendor exposes | Whatever your proxy vendor exposes, wired by hand | Country, region, city and ISP, plus a sticky session id |
| Who maintains it | The vendor, on their roadmap | You: pool, upgrades, proxy wiring, driver versions | We run the pool; you keep your Playwright code |
Interaction
Login state
Isolation
Exit location
Who maintains it
// Exits
Location is part of the connection URL, not a separate proxy integration. Add country for a residential exit there; narrow it with region, city and isp. Leave them off for the default datacenter exit, whose egress is free.
| Param | Type | What it does |
|---|---|---|
country | string | ISO-3166 country code of the residential exit, e.g. "US". Required to enable residential location targeting; omit for the default exit. See GET /api/proxy/locations. |
region | string | Region/state code within the country, e.g. "CA". Optional; narrows the exit. |
city | string | City code within the country/region. Optional; narrows the exit. |
isp | string | ISP code. Optional; narrows the exit to a carrier. |
session | string | Sticky-session id ([A-Za-z0-9]+). When set, the same residential exit IP is held for the session TTL so a multi-step flow keeps one IP. |
GET /api/proxy/locations, GET /api/proxy/locations/countries, GET /api/proxy/locations/regions?country=US, GET /api/proxy/locations/cities?country=US®ion=CA, GET /api/proxy/locations/isps?country=US and returns code / name pairs you can feed back into the URL.country
region
city
isp
session
GET /api/proxy/locations, GET /api/proxy/locations/countries, GET /api/proxy/locations/regions?country=US, GET /api/proxy/locations/cities?country=US®ion=CA, GET /api/proxy/locations/isps?country=US and returns code / name pairs you can feed back into the URL.// Control
The token in the socket URL is the unit you cap, isolate and switch off. Lower one crawler's limit, or revoke its token, without touching the others.
Link tokens live inside a project, separate from your wf_ API tokens. Give each crawler its own and turn one off without touching the rest.
Each link has a connection cap from 1 to 100, default 10. The connection after the cap is refused at the handshake with close code 4003, not queued.
Listing a project’s links returns activeConnections for each: the sockets open on that token right now.
Revoke a leaked token and new connections get close code 4002. Regenerate issues a fresh secret on the same link; an optional expiry retires it on schedule.
When the gateway will not give you a browser, it sends a JSON { "type": "error", "code", "message" } message, then closes the WebSocket with one of these codes. Nothing is left hanging for you to time out.
| Code | Meaning |
|---|---|
| 4001 | Authentication required: token missing. |
| 4002 | Invalid or revoked token. |
| 4003 | Rate limit exceeded: max N concurrent connections for this link. |
| 4004 | No billing account for this link. |
| 4005 | Session not allowed (billing gate, e.g. no active subscription or no remaining session-hours). |
| 4006 | Residential proxy location control is not available on this deployment. |
| 4007 | Invalid proxy location (unknown country/region/city/isp). |
| 4008 | Failed to provision the residential proxy. |
| 4020 | Invalid viewport (not whole pixels, one of the two missing, or out of range). |
| 4021 | Invalid proxy option, or residential parameters combined with an own proxy or proxy=none. |
| 4022 | proxy=own on a link without an own proxy. |
| 4023 | Own proxy resolves to a private, local or reserved address. |
| 4024 | Own proxy unreachable (DNS, refused, timeout, TLS or protocol error). |
| 4025 | Own proxy rejected the username or password. |
| 4026 | Own proxies with a password are not available on this deployment. |
| 4027 | The browser window could not be sized to the viewport. |
| 4402 | Sent mid-session: the balance (or your monthly spend limit) can no longer pay, so the session ended. |
| 4500 | Internal server error. |
4001
4002
4003
4004
4005
4006
4007
4008
4020
4021
4022
4023
4024
4025
4026
4027
4402
4500
// What it costs
Sessions draw on the same credit balance as the Web Data API. The exit you choose is usually the bigger line.
One page held open for an hour, logged in, on a sticky residential IP.
| Item | Credits |
|---|---|
| 1 page open for 60 minutes | 8 |
| 0.15 GB residential traffic (320 / GB) | 48 |
Total
56credits
about $0.70
at $0.0125 / credit
About 27 runs a month on Developer ($19 for 1,520 credits).
No location params: the default exit, whose egress is free.
| Item | Credits |
|---|---|
| 1 page open for 60 minutes | 8 |
| Datacenter egress | 0 |
Total
8credits
about $0.10
at $0.0125 / credit
About 190 runs a month on Developer ($19 for 1,520 credits).
Every page you open is billed for at least 60 seconds. Full rates, plans and credit packs are on the pricing page.
// FAQ
Plan for a cold start. A new browser takes about 45 to 50 seconds to be ready, and up to about 80 seconds with a residential exit, because there is no pre-started pool for CDP sessions. Set your connect timeout above that.
No. The Wayfern Chromium build runs headed on a virtual display (Xvfb) inside our containers, so there is nothing to install or display on your side.
Per open page, pro-rated from 8 credits per open-page hour, with a 60-second minimum on every page you open. Two pages for half an hour cost the same as one page for an hour. Residential traffic adds 320 credits per GB; datacenter egress is free. At zero balance the next connection is refused with close code 4005.
No. There are no persistent customer profiles: cookies and storage live for as long as your socket is open. A browser instance is shared only by open connections with the same link token and exit location, and it is removed when the last of them closes.
When you only need the page. A session suits logins, forms and multi-step flows. For a single fetch, scrape costs 1 credit per page and a screenshot 5 per capture, from the same balance.
Anything that speaks the Chrome DevTools Protocol over a WebSocket: Playwright's connectOverCDP (Node or Python), Puppeteer's connect, or a raw CDP client.
// Keep reading
// Next step
Create a project, mint a link token, and change one line: the connect URL. The rest of your Playwright or Puppeteer code stays as it is.