Naver's Chromium browser
Naver Whale
Naver Whale, the browser that ships with Naver services in Korea. Stable channel.
- Launch
POST https://starter.cdpfleet.com/whale/session- Connect
chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } })- Playwright client
- 1.60.x
- Versions now
- stable5.41.429.5
Example
// npm install [email protected]
// Browsers run on cdpfleet, so no `npx playwright install` is needed.
import { chromium } from 'playwright';
const KEY = process.env.CDPFLEET_API_KEY;
// 1. Launch the browser
const res = await fetch('https://starter.cdpfleet.com/whale/session', {
method: 'POST',
headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
body: JSON.stringify({
proxy: 'http://user:[email protected]:8080',
headless: 'new',
}),
});
if (!res.ok) throw new Error(`launch failed: ${res.status} ${await res.text()}`);
const { wsUrl } = await res.json();
// 2. Connect and drive it
const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close(); // ends the session and stops billing
# pip install playwright==1.60.0 requests
# Browsers run on cdpfleet, so no `playwright install` is needed.
import os
import requests
from playwright.sync_api import sync_playwright
KEY = os.environ["CDPFLEET_API_KEY"]
# 1. Launch the browser
res = requests.post(
"https://starter.cdpfleet.com/whale/session",
headers={"x-api-key": KEY},
json={
"proxy": "http://user:[email protected]:8080",
"headless": "new",
},
timeout=60,
)
res.raise_for_status()
ws_url = res.json()["wsUrl"]
# 2. Connect and drive it
with sync_playwright() as p:
browser = p.chromium.connect(ws_url, headers={"x-api-key": KEY})
page = browser.new_page()
page.goto("https://example.com")
print(page.title())
browser.close() # ends the session and stops billing
// Maven: com.microsoft.playwright:playwright:1.60.0 and com.google.code.gson:gson:2.11.0
// Run with PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1: browsers run on cdpfleet.
import com.google.gson.JsonParser;
import com.microsoft.playwright.*;
import java.net.URI;
import java.net.http.*;
import java.util.Map;
public class Example {
public static void main(String[] args) throws Exception {
String key = System.getenv("CDPFLEET_API_KEY");
// 1. Launch the browser
String body = """
{
"proxy": "http://user:[email protected]:8080",
"headless": "new"
}
""";
HttpResponse<String> res = HttpClient.newHttpClient().send(
HttpRequest.newBuilder(URI.create("https://starter.cdpfleet.com/whale/session"))
.header("x-api-key", key)
.header("content-type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build(),
HttpResponse.BodyHandlers.ofString());
if (res.statusCode() != 200) {
throw new RuntimeException("launch failed: " + res.statusCode() + " " + res.body());
}
String wsUrl = JsonParser.parseString(res.body()).getAsJsonObject().get("wsUrl").getAsString();
// 2. Connect and drive it
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().connect(wsUrl,
new BrowserType.ConnectOptions().setHeaders(Map.of("x-api-key", key)));
Page page = browser.newPage();
page.navigate("https://example.com");
System.out.println(page.title());
browser.close(); // ends the session and stops billing
}
}
}
// dotnet add package Microsoft.Playwright --version 1.60.0
// Browsers run on cdpfleet, so no `playwright install` is needed.
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using Microsoft.Playwright;
var key = Environment.GetEnvironmentVariable("CDPFLEET_API_KEY")!;
// 1. Launch the browser
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", key);
var body = """
{
"proxy": "http://user:[email protected]:8080",
"headless": "new"
}
""";
var res = await http.PostAsync("https://starter.cdpfleet.com/whale/session",
new StringContent(body, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
throw new Exception($"launch failed: {(int)res.StatusCode} {await res.Content.ReadAsStringAsync()}");
var wsUrl = (await res.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("wsUrl").GetString()!;
// 2. Connect and drive it
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Chromium.ConnectAsync(wsUrl, new BrowserTypeConnectOptions
{
Headers = new Dictionary<string, string> { ["x-api-key"] = key },
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://example.com");
Console.WriteLine(await page.TitleAsync());
await browser.CloseAsync(); // ends the session and stops billing
// go get github.com/playwright-community/[email protected]
//
// One-time driver setup (needs Node.js 18+): playwright-go can no longer download the
// 1.60.0 driver itself, so build it from npm and point PLAYWRIGHT_DRIVER_PATH at it:
// mkdir -p ~/.cache/pw-driver-1.60.0 && cd ~/.cache/pw-driver-1.60.0 &&
// curl -sL https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz | tar xz &&
// ln -sf "$(command -v node)" node
// export PLAYWRIGHT_DRIVER_PATH=~/.cache/pw-driver-1.60.0
// Browsers run on cdpfleet, so no browsers are downloaded (SkipInstallBrowsers).
package main
import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"strings"
"github.com/playwright-community/playwright-go"
)
func main() {
key := os.Getenv("CDPFLEET_API_KEY")
// 1. Launch the browser
body := `{
"proxy": "http://user:[email protected]:8080",
"headless": "new"
}`
req, _ := http.NewRequest("POST", "https://starter.cdpfleet.com/whale/session", strings.NewReader(body))
req.Header.Set("x-api-key", key)
req.Header.Set("content-type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
msg, _ := io.ReadAll(res.Body)
log.Fatalf("launch failed: %s %s", res.Status, msg)
}
var session struct {
WsURL string `json:"wsUrl"`
}
if err := json.NewDecoder(res.Body).Decode(&session); err != nil {
log.Fatal(err)
}
// 2. Connect and drive it (Install fetches only the Playwright driver, once)
opts := &playwright.RunOptions{SkipInstallBrowsers: true}
if err := playwright.Install(opts); err != nil {
log.Fatal(err)
}
pw, err := playwright.Run(opts)
if err != nil {
log.Fatal(err)
}
defer pw.Stop()
browser, err := pw.Chromium.Connect(session.WsURL, playwright.BrowserTypeConnectOptions{
Headers: map[string]string{"x-api-key": key},
})
if err != nil {
log.Fatal(err)
}
page, err := browser.NewPage()
if err != nil {
log.Fatal(err)
}
if _, err := page.Goto("https://example.com"); err != nil {
log.Fatal(err)
}
title, _ := page.Title()
fmt.Println(title)
browser.Close() // ends the session and stops billing
}
Want different options or a specific version? Open this browser in the code builder.
Launch options
Send these as the JSON body of the launch request. Everything is optional; unknown fields are ignored. Context options such as viewport, user agent or cookies are set client-side after connecting (browser.newContext({...})).
| Option | Type | Default | What it does |
|---|---|---|---|
proxy | string | object | array | the free Community proxy | A proxy URL ( Use it for: Every session that needs a particular exit: your own proxy for the IP and country you choose, a list as a pool to spread requests across IPs, or a Optional: without a proxy (and without Example: "http://user:[email protected]:8080" |
proxy_rules | array | none | A list of Use it for: Scrapers that want an expensive exit only where it matters: residential for the target site, a cheaper proxy for images, CDNs and APIs, or a different country per host. Needs Example: [{"name":"dc-assets","hosts":["*.cloudfront.net","static.example.com"],"proxy":["http://u:[email protected]:4444","http://u:[email protected]:4444"]}] |
inactivity_timeout | number | string | 120s | The timer resets on any protocol traffic in either direction. A number of seconds ( Use it for: Freeing the thread quickly when a script dies (short), or giving a person or a slow step time to think (long), up to your plan's cap. Up to 10 minutes on shared threads, 5 on Community and 24 h on dedicated; a longer value is cut to your plan's maximum. Idle minutes are billed like any others. A slow page load through a proxy can produce no traffic for a while, so keep it at 60 s or more. Never longer than the session's overall timeout. Example: "2m" |
overall_timeout | number | string | your plan's maximum session length (24 h) | A number of seconds ( Use it for: A hard time budget per job, so a stuck loop can't hold a thread for hours: batch scrapers and agents with a deadline. Example: "30m" |
memory | integer | 1 | A multiplier of your plan's per-session memory (Community 2 GB, shared 4 GB, dedicated 6 GB; headful sessions get 512 MB more for the display). The session's weight is multiplied too: headless with Use it for: Memory-heavy work that would otherwise end with Only Example: 2 |
downloads | boolean | false | With Use it for: Downloads you can't catch in code: a person clicking in live view or Launch interactive, an AI agent working through the MCP server, a script that crashed or disconnected ( Paid plans ( Example: true |
vpn_tunnel | boolean | false | The browser gets a network of its own whose only way out is the session's residential proxy, so UDP works as on a home connection: WebRTC shows the residential exit IP as its public address, and HTTP/3 (QUIC) works. Without it, a proxied session sends no UDP at all — WebRTC shows only an anonymous mDNS name and sites fall back to HTTP/2. The TLS fingerprint and the exit IP are the same either way. It is not a VPN: the residential proxy still opens the connections to the site, so the TCP/IP fingerprint the site sees is the exit device's, as without the tunnel. UDP traffic is billed at the token's tier and listed as host Use it for: Work that needs WebRTC or HTTP/3 to look like a home connection, such as video calls or sites that compare the WebRTC address with the page's IP, on a sticky residential exit. Needs the default Example: true |
vpn | string | object | none | The name of a VPN saved on the dashboard's Proxies → My VPNs page ( Use it for: Dedicated-plan users who already run their own VPN (WireGuard, AmneziaWG, OpenVPN, sing-box): the whole browser, UDP included, leaves through it, so pages and WebRTC show the VPN's address. Dedicated plans only ( Example: "home-wg" |
headless | boolean | "new" | "shell" | false (headful, on a virtual display) |
Use it for: Bulk scraping and tests where nobody watches: lower memory and faster launches. Leave it headful (the default) when a site checks for headless tells, or when you want a recording or live view without extra options. Example: "new" |
channel | "stable" | "beta" | "dev" | "developer" | stable | Omit it (or send Use it for: QA teams checking a site against the next browser release before users get it (beta, dev), and scrapers that want to see a change coming. Opera calls its dev channel Example: "beta" |
screencast | boolean | false | Not a launch option: the generated program calls Use it for: Watching or recording a headless page from your own code as a stream of JPEG frames, for debugging dashboards or human review, without a full live view. View-only and only for the client that drives the page. With Example: true |
live_view | boolean | false (headful sessions are always watchable) | Headful sessions can always be watched or taken over ( Use it for: A person watching or taking over the session in the browser (a login, a 2FA step, a check that needs a human), or recording a headless session. Headless Chromium-based browsers only (Camoufox, Firefox and WebKit are watchable when headful, their default). Doesn't give your code CDP access — add Example: true |
screen_size | "WxH" | { width, height } | the server's display (1280×720 window) | Sets the screen the browser runs on, from 640×480 to 3840×2160, e.g. Use it for: Responsive and visual tests at a given resolution, and personas that need a common desktop or laptop screen instead of the server default. Headful sessions only — a headless launch with Example: "1366x768" |
keep_alive | boolean | false | A disconnect no longer ends the session: reconnect to the same Use it for: Agents and step-by-step workflows that disconnect between steps, and code that may lose its connection, without losing the logged-in browser. Not on Rebrowser. Dedicated threads only ( Example: true |
start_url | string (http/https URL) | none | The server opens the page itself and the session doesn't wait for your code to connect: open a live link in Use it for: A person logs in or passes a check by hand in live view first; then your code connects and carries on in the same page. http or https, at most 2,048 characters ( Example: "https://example.com" |
record | boolean | true (when recording applies) | Sessions are recorded by default within your recording limits: headful sessions from their display, headless sessions only when launched with Use it for: Keeping a video of each run for debugging, QA evidence or audits. Set Recording applies to headful sessions and to headless ones launched with Example: false |