Session Lifecycle
Understanding what happens when connections open and close through the gateway.
Understanding what happens when connections open and close through the gateway.
What the Gateway Manages
The gateway manages the transport layer - the WebSocket pipe between your client and the provider. When a client connects, the gateway:
- Selects a provider
- Opens a TCP connection to the provider
- Forwards the WebSocket upgrade handshake
- Pipes all traffic bidirectionally
- Tracks the session (connected time, message count, provider assignment)
When either side disconnects, the gateway:
- Closes the other side of the pipe
- Releases the concurrency slot for that provider
- Updates metrics (duration, latency)
- Removes the session from tracking
What the Gateway Does NOT Manage
The gateway does not manage provider-specific session lifecycle. Some providers have their own concept of "sessions" that exist independently of the WebSocket connection.
For example:
- Some cloud providers keep a browser session alive after you disconnect, expecting you to explicitly release it
- Some providers auto-timeout idle sessions after a period (e.g., 15 minutes)
- Self-hosted Playwright servers close the browser when the WebSocket closes (no cleanup needed)
- Raw Chrome instances stay alive after the WebSocket disconnects
The gateway has no knowledge of these provider-specific behaviors. It's a transparent transport layer.
Best Practices
Self-hosted providers (Playwright, Chrome)
No special cleanup needed. The browser process is managed by the provider.
const browser = await chromium.connect('ws://gateway:9500/v1/connect');
await page.goto('https://example.com');
await browser.close(); // WebSocket closes, gateway cleans up. Done.Cloud providers with managed sessions
If your cloud provider has session management (create/release lifecycle), handle it in your application code:
// Your application manages the provider session
const session = await provider.sessions.create();
// Connect through the gateway
const browser = await chromium.connectOverCDP('ws://gateway:9500/v1/connect');
await page.goto('https://example.com');
await browser.close();
// Your application releases the provider session
await provider.sessions.release(session.id);The gateway handles the WebSocket transport. Your application handles the provider session lifecycle.
Why It Works This Way
The gateway is protocol-agnostic. It forwards raw bytes without understanding what they mean. This is what makes it work with any provider and any protocol. The trade-off is that provider-specific lifecycle management stays in the application layer, where it belongs.
This is the same model used by reverse proxies like nginx and HAProxy - they manage TCP/HTTP connections, not application sessions.
Connection States in the Gateway
You can check active sessions at any time:
curl http://localhost:9500/v1/sessions{
"count": 2,
"sessions": [
{
"id": "abc123",
"providerId": "primary",
"connectedAt": "2026-03-25T17:00:00.000Z",
"lastActivity": "2026-03-25T17:05:30.000Z",
"durationMs": 330000,
"messageCount": 847
}
]
}Idle Timeout
The gateway has a configurable idle timeout. If no WebSocket messages pass through a session for the configured period, the gateway closes both sides:
gateway:
sessions:
idleTimeoutMs: 300000 # 5 minutes (default)This protects against leaked connections where a client disconnects without a proper close handshake (e.g., network drop, crashed process). The provider's concurrency slot is freed when the idle timeout fires.
Session Reconnection
What the gateway does: when a client connects, the gateway assigns a session ID and remembers which provider that session was routed to. If the client disconnects and reconnects with the same session ID within the reconnect timeout, the gateway sends it back to the same provider.
What the gateway does not do: the gateway does not keep the browser alive itself. It does not buffer messages, replay CDP traffic, or hold any browser state on its side. Persistence depends entirely on the provider continuing to run the browser after the WebSocket closes.
This works because:
- Raw Chrome (
--remote-debugging-port) keeps the browser process running after a CDP client disconnects. - Cloud providers like Browserless, Steel, and similar keep browsers alive for a configured timeout after disconnect.
It does not work when the provider kills the browser on disconnect (for example, a self-hosted Playwright server that ties the browser lifetime to the WebSocket).
When persistence does work, all browser state (cookies, localStorage, open tabs, page content, scroll position) is intact on reconnect, because the same browser process is still running on the provider.
gateway:
sessions:
reconnectTimeoutMs: 300000 # 5 minutes (default)To reconnect, pass the session ID as a query parameter:
ws://gateway:9500/v1/connect?sessionId=<session-id>The session ID is returned in the X-Session-Id response header when a connection is first established.
This is useful for:
- AI agents that crash mid-workflow and need to resume
- Long-running automation jobs interrupted by network issues
- Multi-step processes where losing browser state means starting over
Limitations:
- Only works with Puppeteer and raw CDP clients (Playwright destroys contexts on disconnect)
- Does not survive gateway restart (session registry is in-memory)
- Does not survive browser crash at the provider