# Browserbase

**A cloud browser is a proxy problem in disguise: it launches from someone else's datacenter, on someone else's IPv4 pool, and every site it touches sees a shared, anonymous exit.**

No identity, no accountability, nothing to hand a downstream API that expects a stable address; bot-detection flags the pool on sight, and when something goes wrong there's no address to point at. Point Browserbase's external-proxy option at Whisper instead and the browser leaves from your agent's own routable, DNSSEC-signed, RDAP-documented IPv6 `/128` — the same identity it uses everywhere else, provable by anyone with `dig -x` and no key.

This page is the BYOP (Bring-Your-Own-Proxy) recipe: how Browserbase's `proxies` field maps onto the Whisper egress, the exact wire mechanics, and a runnable two-tier example.

## Why this is worth doing

- **Real IPv6, not a shared IPv4 pool.** Browserbase's own egress is IPv4-only. Routing a session through Whisper is the only way to give a Browserbase browser a **routable IPv6** identity at all.
- **The address is independently verifiable.** Anyone — not just you — can run `dig -x` or an RDAP lookup against the IP a page saw and get back a real, signed answer: *this is a Whisper agent, here's its identity record.* Compare that to a rotating proxy-pool IP, which resolves to nothing.
- **No infrastructure to run.** No VPN box, no sidecar, no proxy fleet to keep alive. The egress is a standing Whisper service; you supply a bearer credential, Browserbase does the rest.
- **Keyless half works today, for anyone.** You don't need a Whisper account to *check* whether a given IP is a Whisper agent — that's public infrastructure, covered below.

## The mechanism: BYOP is just HTTP CONNECT with Basic auth

Browserbase's `sessions.create` accepts a `proxies` array. For an `external` proxy, Browserbase's browser issues an `HTTP CONNECT` to your `server` for every outbound TCP stream and authenticates with a `Proxy-Authorization: Basic <base64(username:password)>` header — [RFC 7617](https://www.rfc-editor.org/rfc/rfc7617) Basic auth, carried over the [RFC 9110 §9.3.6](https://www.rfc-editor.org/rfc/rfc9110#section-9.3.6) `CONNECT` method. That's it — no proprietary SDK on the wire, just a standard forward-proxy handshake:

```
CONNECT v6.ident.me:443 HTTP/1.1
Host: v6.ident.me:443
Proxy-Authorization: Basic dzp3aGlzcGVyX2VncmVzc19iZWFyZXI...
```

Whisper's egress tier (`connect.whisper.online:443`) speaks exactly this: username `w`, password = the agent's egress bearer (issued at `register`/`connect` time), and it source-binds every accepted stream to the agent's `/128` via `IP_FREEBIND` + AnyIP over `2a04:2a01::/32` before it ever touches the target. Browserbase validates the proxy **at `sessions.create`**, not mid-run — a bad bearer fails immediately with a clear error, not a silent hang three requests in.

```js
const session = await bb.sessions.create({
  projectId,
  proxies: [{
    type: "external",
    server: "http://connect.whisper.online:443",
    username: "w",
    password: process.env.WHISPER_PROXY_PASS, // the agent's egress bearer
  }],
});
const browser = await chromium.connectOverCDP(session.connectUrl);
```

> External proxies on Browserbase are a **paid-plan** feature — on the free tier, `sessions.create` with a non-empty `proxies` array returns `402 Payment Required`. This is a Browserbase account gate, not a Whisper one: the keyless verify tier below works on **any** Browserbase plan, or with no Browserbase account at all.

## With stock tools

Everything Whisper emits here is a standard record — you can drive the whole loop with `dig`, `curl`, and `openssl`, no Whisper software involved.

**Prove the demo agent's address is a real, delegated, DNSSEC-signed identity:**

```sh
# Forward: name -> address, DNSSEC-validated
dig +dnssec AAAA acef2002a323d40d4.demo.agents.whisper.online

# Reverse: address -> name (the PTR Whisper synthesizes for every /128 it allocates)
dig -x 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4

# RDAP: the IP-anchored assignment document (RFC 7482/9083) — who holds this /128, since when
curl -s https://rdap.whisper.online/ip/2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4 | jq .
```

**Confirm the TLS identity presented on that address is bound to it (DANE, [RFC 6698](https://www.rfc-editor.org/rfc/rfc6698)):**

```sh
kdig +short TLSA _443._tcp.acef2002a323d40d4.demo.agents.whisper.online
# 3 1 1 b653a4ef...fcb82d1d

openssl s_client -connect '[2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4]:443' \
  -servername acef2002a323d40d4.demo.agents.whisper.online < /dev/null 2>/dev/null \
  | openssl x509 -noout -pubkey \
  | openssl pkey -pubin -outform der | openssl sha256
# compare the hash against the TLSA record above
```

**Drive Browserbase's proxy manually** — no Whisper SDK, plain `curl` through the CONNECT tunnel to sanity-check the credential before wiring it into Playwright:

```sh
curl -v -x http://w:$WHISPER_PROXY_PASS@connect.whisper.online:443 \
  https://v6.ident.me/
# -> the agent's own /128, printed back by the echo service
```

## With Whisper

The [`whisper-examples/browserbase`](https://github.com/whisper-sec/whisper-examples/tree/main/browserbase) recipe wraps exactly this into a two-tier script — [`whisper-edge`](/docs/sdk-edge) for the keyless half, the Browserbase JS SDK + Playwright for the keyed half.

**Tier 1 — keyless verify (no key, no Browserbase account needed):**

```sh
node index.mjs verify 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
# { "address": "2a04:2a01:...", "is_whisper_agent": true, "rdap": { ... } }
```

**Tier 2 — keyed egress (needs a Browserbase key + a provisioned agent):**

```sh
# provision an agent + egress credential, if you don't have one yet
whisper create my-browser-agent
# -> address: 2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4
# -> egress bearer printed once; store it, never log it

export BROWSERBASE_API_KEY=bb_...
export BROWSERBASE_PROJECT_ID=...
export WHISPER_PROXY_PASS=...          # the egress bearer from `whisper create`
export WHISPER_AGENT_128=2a04:2a01:eb5a:ca74:cef2:2a:323d:40d4

node index.mjs
# { "tier": "egress", "session": "...", "seen_ip": "2a04:2a01:...",
#   "is_whisper_agent": true, "expected": "2a04:2a01:...", "matches_agent_address": true }
```

Same provisioning call, over the control plane directly:

```
CALL whisper.agents({op:'register', args:{label:'my-browser-agent'}})
CALL whisper.agents({op:'connect', args:{agent:'my-browser-agent', tier:'socks5'}})
  -> { http_proxy, connection_string, dns, doh_url }
```

`http_proxy` is exactly the `server`/`username`/`password` triple Browserbase's `proxies` array expects — copy it in directly. The example script closes the loop itself: after the browser loads the IPv6 echo page, it calls `verify(seen_ip)` from `whisper-edge` and asserts the observed address is both a real Whisper agent *and* the specific `/128` you expected — proving, end to end, that the cloud browser's traffic left from your agent's identity, not a shared pool.

## Where this fits

Browserbase is one BYOP consumer of the same egress tier that backs every Whisper connect path — see [Connect](/docs/connect) for the full tier ladder (routed WireGuard, SOCKS5/HTTP egress, DoH-only). If you'd rather not hold an egress bearer in your automation at all, the [forward gateway](/docs/forward-gateway) offers a single stateless `fetch`-style endpoint that sources from your agent's `/128` without a persistent proxy connection — a good fit for one-shot page fetches instead of a full interactive browser session.

## Next

Continue with [Connect](/docs/connect) for the full egress tier reference, or [The forward gateway](/docs/forward-gateway) for a proxy-less alternative.
