Cookies & sessions
The headline question this guide answers is: how do I make the next session look like the same user as the last one? Two pieces of state control that — the cookies the browser is carrying, and the fingerprint the browser is wearing — and both are designed to survive across rentals.
- Cookies live in the browser context.
GetCookies/SetCookies/ClearCookiesare the entire surface. - The cookie identity is the
(name, domain, path)tuple.SetCookiesupserts on that key. - Every session is rented with a fingerprint id — either one you pin via
WithFingerprint(...)/withFingerprint(...), or one the server picks based on your country code. Read it back withbrowser.Fingerprint()(Go) /browser.getFingerprint()(TS). - To impersonate the same user across runs: store the fingerprint id + cookies on first rental, replay both on the next.
ConnectSession(Go) /createBrowserClient(TS) reattach to an existing rental from another process. They do not extend the rental lifetime.
The cookie surface
There are exactly three calls, and they all operate on the whole browser context (across every tab and frame). No flags, no filters — read everything, write everything, clear everything.
// Read all cookies.
cookies, _ := browser.GetCookies(ctx)
for _, c := range cookies {
fmt.Println(c.Name, "=", c.Value, "@", c.Domain, c.Path)
}
// Write or upsert cookies.
_ = browser.SetCookies(ctx, []wrc.CookieParam{
wrc.Cookie("session", "abc123", "example.com", "/"),
wrc.Cookie("locale", "en-US", "example.com", "/"),
})
// Wipe all cookies (no per-cookie deletion).
_ = browser.ClearCookies(ctx)const cookies = await browser.getCookies();
for (const c of cookies) {
console.log(c.name, "=", c.value, "@", c.domain, c.path);
}
await browser.setCookies([
{ name: "session", value: "abc123", domain: "example.com", path: "/" },
{ name: "locale", value: "en-US", domain: "example.com", path: "/" },
]);
await browser.clearCookies();A CookieParam is four fields, all required: name, value,
domain, path. The server uses the (name, domain, path) tuple
as the cookie's identity — calling SetCookies with the same tuple
overwrites the existing cookie rather than adding a duplicate.
Persisting cookies between runs
The simplest "make me look logged in next time" recipe: do the login once, dump the cookie jar to disk, restore it on the next rental.
// First run: log in, then persist.
_, _ = browser.Navigate(ctx, "https://example.com/login", 0)
_, _ = browser.Fill(ctx, wrc.CSS("#email"), "alice@example.com")
_, _ = browser.Fill(ctx, wrc.CSS("#password"), "hunter2")
_, _ = browser.Click(ctx, wrc.CSS("button[type=submit]"))
cookies, _ := browser.GetCookies(ctx)
buf, _ := json.Marshal(cookies)
_ = os.WriteFile("cookies.json", buf, 0o600)
// Next run (same script or a different process): restore.
buf, _ := os.ReadFile("cookies.json")
var cookies []wrc.CookieParam
_ = json.Unmarshal(buf, &cookies)
_ = browser.SetCookies(ctx, cookies)
_, _ = browser.Navigate(ctx, "https://example.com/dashboard", 0)// First run.
await browser.navigate("https://example.com/login");
await browser.fill(css("#email"), "alice@example.com");
await browser.fill(css("#password"), "hunter2");
await browser.click(css("button[type=submit]"));
const cookies = await browser.getCookies();
await fs.writeFile("cookies.json", JSON.stringify(cookies), { mode: 0o600 });
// Next run.
const restored = JSON.parse(await fs.readFile("cookies.json", "utf8"));
await browser.setCookies(restored);
await browser.navigate("https://example.com/dashboard");Two things this doesn't solve on its own:
- The new session ships with a different fingerprint — same cookies on a brand-new browser identity is the classic "weird session" signal that triggers re-auth on cautious sites.
- The new session is on a different exit IP — a session cookie followed by an IP swap can also force a re-login.
Both are fixable. The fingerprint side is the rest of this guide;
the IP side comes down to reusing the same proxy (or pinning a
country code if you're using managed proxies — see
BrowserConfig /
TS).
Session lifecycle in one paragraph
Quick refresher (covered in depth in
Core concepts): RentBrowser rents a session for
the duration in your config and hands you a CloudBrowser handle.
The session lives until the rental timer runs out or you call
StopBrowser / Close. From a different process, ConnectSession
(Go) / createBrowserClient (TS) attaches to an existing session by
its id and gRPC URL — useful when you persisted those across a
restart. Closing an attached handle closes only the connection, the
underlying rental keeps running until the rent timer expires or
someone calls the standalone wrc.StopBrowser(apiKey, sessionId).
That gives you the second persistence axis: you can pause your code, restart your process, and reattach to the same browser without ever losing cookies, navigation state, or fingerprint.
Fingerprint persistency
This is where WRC differs from spinning up a fresh headless Chrome
every run. Each rental is provisioned with a fingerprint — a
server-side bundle of UA string, navigator.* quirks, screen size,
canvas/audio/WebGL responses, etc. — and that fingerprint has a
stable id you can hold on to and replay.
Default: server picks per country
If you don't pass WithFingerprint / withFingerprint, the server
picks one for you based on the rental's country code. Different
rentals → different fingerprints, every run.
Pinning: read it, store it, send it back
After every rental you can read which fingerprint the session is wearing:
browser, _ := wrc.RentBrowser(ctx, cfg)
fmt.Println("fingerprint id:", browser.Fingerprint())const browser = await rentBrowser(cfg);
console.log("fingerprint id:", browser.getFingerprint());Hold on to that string and feed it back into the next rental to get the exact same browser identity again:
cfg := wrc.NewBrowserConfig(apiKey, 600, "", 0, "", "").
WithCountryCode("DE").
WithFingerprint(savedFingerprint)
browser, _ := wrc.RentBrowser(ctx, cfg)const cfg = new BrowserConfig(apiKey, 600, "", 0, "", "")
.withCountryCode("DE")
.withFingerprint(savedFingerprint);
const browser = await rentBrowser(cfg);What "same identity" gets you in practice:
- UA string stays identical (Chrome version, OS, build).
navigator.*properties match (platform, hardwareConcurrency, deviceMemory, plugins, etc.).- Canvas/audio/WebGL fingerprints reproduce — the deterministic pixel data and audio buffer the site can hash for tracking.
- Screen + viewport defaults stay the same.
- Locale + timezone carry across when you keep the same
WithCountryCode/WithTimezone.
To the target site, that's a returning user, not a new browser install on the same account.
Stable identity across runs — putting it together
Cookies + fingerprint + matching geography is the full "this is the same user" combo. The first run captures everything; later runs restore it:
// ── First run ──
cfg := wrc.NewBrowserConfig(apiKey, 600, "", 0, "", "").
WithCountryCode("DE")
browser, _ := wrc.RentBrowser(ctx, cfg)
_, _ = browser.Navigate(ctx, "https://example.com/login", 0)
// … perform login …
cookies, _ := browser.GetCookies(ctx)
identity := struct {
Fingerprint string `json:"fingerprint"`
Country string `json:"country"`
Cookies []wrc.CookieParam `json:"cookies"`
}{
Fingerprint: browser.Fingerprint(),
Country: browser.CountryCode(),
Cookies: cookies,
}
buf, _ := json.Marshal(identity)
_ = os.WriteFile("identity.json", buf, 0o600)
_ = browser.Close()
// ── Later run (same or different process) ──
buf, _ := os.ReadFile("identity.json")
var saved struct {
Fingerprint string
Country string
Cookies []wrc.CookieParam
}
_ = json.Unmarshal(buf, &saved)
cfg = wrc.NewBrowserConfig(apiKey, 600, "", 0, "", "").
WithCountryCode(saved.Country).
WithFingerprint(saved.Fingerprint)
browser, _ = wrc.RentBrowser(ctx, cfg)
_ = browser.SetCookies(ctx, saved.Cookies)
_, _ = browser.Navigate(ctx, "https://example.com/dashboard", 0)// ── First run ──
const cfg = new BrowserConfig(apiKey, 600, "", 0, "", "")
.withCountryCode("DE");
let browser = await rentBrowser(cfg);
await browser.navigate("https://example.com/login");
// … perform login …
const identity = {
fingerprint: browser.getFingerprint(),
country: "DE",
cookies: await browser.getCookies(),
};
await fs.writeFile("identity.json", JSON.stringify(identity), { mode: 0o600 });
await browser.stopBrowser();
// ── Later run ──
const saved = JSON.parse(await fs.readFile("identity.json", "utf8"));
const reCfg = new BrowserConfig(apiKey, 600, "", 0, "", "")
.withCountryCode(saved.country)
.withFingerprint(saved.fingerprint);
browser = await rentBrowser(reCfg);
await browser.setCookies(saved.cookies);
await browser.navigate("https://example.com/dashboard");If you're routing through your own proxy, persist
(proxyHost, proxyPort, proxyUsername, proxyPassword) the same way
— a stable exit-IP region on top of stable cookies and a stable
fingerprint is the cleanest "returning user" signal you can send.
Reattaching to a live session
For long-running flows that outlive your process — overnight crawls,
human-in-the-loop pauses, reconnecting after a script crash — store
the sessionId and grpcUrl and reattach later:
// First process: rent, persist the address.
browser, _ := wrc.RentBrowser(ctx, cfg)
_ = os.WriteFile("session.json", []byte(fmt.Sprintf(
`{"sessionId":%q,"grpcUrl":%q}`,
browser.SessionId(), browser.GrpcUrl(),
)), 0o600)
// Do NOT call Close — the rental should keep running.
// Later process: reattach to the same session.
buf, _ := os.ReadFile("session.json")
var s struct{ SessionId, GrpcUrl string }
_ = json.Unmarshal(buf, &s)
browser, _ = wrc.ConnectSession(ctx, s.GrpcUrl, apiKey, s.SessionId)
// browser drives the same context as before — same cookies, same fingerprint,
// same open page.// First process.
const browser = await rentBrowser(cfg);
await fs.writeFile("session.json", JSON.stringify({
sessionId: browser.getSessionId(),
}), { mode: 0o600 });
// Do NOT call stopBrowser — keep the rental alive.
// Later process. The TS SDK doesn't expose the rented gRPC URL on
// the CloudBrowser handle, so you need to know your transport URL
// out-of-band (typically a known cluster endpoint).
const s = JSON.parse(await fs.readFile("session.json", "utf8"));
const browser2 = createBrowserClient(GRPC_WEB_URL, s.sessionId, apiKey);
// browser2 drives the same context as before.A reattached handle is a thin wrapper: closing it only closes the
transport, not the rental. To actually release the rental from a
process that only has the id, use the standalone
wrc.StopBrowser(ctx, apiKey, sessionId) (Go) /
stopBrowser(apiKey, sessionId) (TS).
Gotchas
- Cookies are upserted, not appended.
SetCookieswith an existing(name, domain, path)tuple replaces the value — handy for refreshing a session cookie, surprising if you expected a duplicate entry. There is no per-cookie delete; clear the whole jar withClearCookiesif you need to start over. CookieParamis intentionally minimal. It carries justname/value/domain/path. Server-side, cookies are written with reasonable defaults — there are no SDK-level knobs forexpires,Secure,HttpOnly, orSameSitetoday.- Fingerprint ids are server-side handles. They're meaningful as long as the server still has that fingerprint in its catalog. If you persist an id for months and later get a "fingerprint not found" error on rental, fall back to letting the server pick a new one and persist the new id.
- Don't mix and match identity signals. Replaying cookies and a US fingerprint while exiting through a DE IP is a louder signal than running with all-defaults. If you persist one of the three (fingerprint / cookies / IP region), persist all three.
- Reattaching is not the same as resuming a stopped session.
ConnectSession/createBrowserClientassumes the rental is still alive on the server. Once the rent duration expires or someone explicitly callsStopBrowser, the context is gone — its cookies, fingerprint and open pages disappear with it. - Closing an attached handle doesn't release the rental. That's
intentional — multiple processes can attach to the same session.
Use the standalone
StopBrowser(ctx, apiKey, sessionId)when you do want to release from outside the renting process.
- Core concepts — the rent → drive → stop lifecycle.
- Quickstart —
BrowserConfigparameters in context. - API reference: Go
RentBrowser/BrowserConfig/GetCookies· TSrentBrowser/BrowserConfig/getCookies.