Migrating from Browserless
You have a Browserless deployment (cloud or self-hosted) and want to
scope a port to Driftstack without surprises. The core difference:
Browserless hands you a remote Chrome to script over CDP (the
Chrome DevTools Protocol — the wire protocol Puppeteer speaks);
Driftstack is action-based — every browser step is a typed HTTPS
call against an iPhone Safari session, and there is no
/function-style endpoint that runs your JavaScript server-side.
Surface comparison
| Browserless | Driftstack |
|---|---|
Connect Puppeteer to wss://chrome.browserless.io?token=… | POST /v1/sessions, then drive with action endpoints (navigate, interact, wait, extract, capture). No CDP passthrough. |
/function (POST a JS body, get JSON back) | No direct equivalent. Express the flow as actions; structured reads go through extract. |
/screenshot + /pdf | POST /v1/sessions/:id/capture with kind: screenshot, dom_snapshot, or pdf. Bytes come back inline (base64). |
| Concurrency you meter yourself | Tier-driven cap; going over returns a 429 with the stable concurrency-limit problem type — see Concurrency & backpressure. |
| Stealth flags / fingerprint plugins | Archetypes (the device + OS + browser identity a session presents) plus server-side profiles. |
The first port: replace the WebSocket
A typical Browserless integration is a Puppeteer script pointed at their WebSocket endpoint:
const browser = await puppeteer.connect({
browserWSEndpoint: `wss://chrome.browserless.io?token=${TOKEN}`,
});
const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
await browser.close();
On Driftstack the same flow is discrete calls:
import { Driftstack } from '@driftstack/sdk';
const client = new Driftstack({ apiKey: process.env.DRIFTSTACK_API_KEY! });
const session = await client.sessions.create({ label: 'title-check' });
try {
await client.sessions.navigate(session.id, { url: 'https://example.com' });
const state = await client.sessions.getState(session.id);
console.log(state.title); // the page title; state.url is the resolved URL
} finally {
await client.sessions.destroy(session.id);
}
No server-side JS anywhere: a login + scrape is a handful of POSTs
followed by a state or
extract read.
Profile persistence: the differentiator
Browserless gives you a fresh browser per connection unless you manage your own cookie jar. Driftstack persists cookies, local storage, and IndexedDB server-side in a named profile:
const profile = await client.profiles.create({ name: 'evergreen-scraper' });
// Launch a session already bound to the profile (one round trip),
// drive the login once, and destroy cleanly.
const session = await client.profiles.launch(profile.id);
try {
await client.sessions.navigate(session.id, { url: 'https://example.com/login' });
// … drive the login with interact / login …
} finally {
await client.sessions.destroy(session.id);
}
// Keep a restore point of the logged-in state.
await client.profileSnapshots.capture(profile.id, { label: 'logged-in-baseline' });
Later sessions bind the same state by passing profile_id on
POST /v1/sessions (or launch again). A profile allows one live
session at a time — a second create against it returns
409 profile-in-use naming the active session, so two runs can’t
overwrite each other’s cookies. Snapshots restore into a new
profile whenever you need to fork or roll back state. Full flow:
Profile management.
Picking a tier
Concurrency is the sizing axis (see the
full cap table): api_starter runs 2
sessions at once, api_builder 8, api_scale 24, and Enterprise
starts at 32 with per-account overrides above that. Prices:
driftstack.dev/pricing.
What Driftstack deliberately doesn’t do
- Raw WebSocket / CDP passthrough. The action endpoints are the contract; arbitrary protocol messages are blocked on purpose.
- Server-side JS execution. No
/functionequivalent. Express the flow as actions, or do the computation client-side over anextract/dom_snapshotread. - Browser choice. Driftstack is iPhone Safari (WebKit) by product scope — it is not a Chrome or Firefox automation service.
- Session video recordings. Not offered today; per-step
capturecalls are the observation tool.
Self-hosting, on the other hand, does exist — as its own offering rather than a toggle on the cloud plans. See driftstack.dev/self-hosted.
Migration checklist
- Inventory your Browserless calls — count
/functionvs/screenshotvs raw-CDP usage. - If
/functionbodies do non-trivial in-page computation, or raw CDP is a large share of your traffic, email [email protected] before starting — the action surface may not cover you yet, and it’s cheaper to learn that first. - Port one job with the TypeScript, Python, or Go SDK.
- Subscribe to webhooks for
session.completed/session.failedand delete your polling loops. - Check the usage surfaces — usage + quotas and cost monitoring — before flipping production traffic.
Need help porting?
Email [email protected] with
your /function bodies and call volumes; that’s enough to scope
whether the action surface covers the workload and which tier fits.